实现通讯,我们首先要知道是怎么样的一个流程,下图是我画的一个通讯流程图:
一.Linux服务器端
我是在Ubuntu20.04下进行的,使用的是C++,引入头文件socket.h
socket()
创建一个socket套接字,使用socket()函数。我们可以定义一个int类型用于接收返回值,来判断socket是否建立成功
int socket_fd = socekt(AF_INET, SOCK_STREAM, 0);
若建立失败,返回-1
sockaddr_in结构体
创建一个sockaddr_in结构体,存储网络地址(IP,Port)
struct sockaddr_in local_addr;
local_addr.sin_family = AF_INET;
local_addr.sin_port = htons(端口号);
local_addr.sin_addr.s_addr = inet_addr("IP地址");
bind()
使用bind()函数绑定套接字。若绑定失败,返回-1。
int Bind = bind(socket_fd,(struct sockaddr *)&local_addr, sizeof(local_addr));
listen()
设置监听,该函数最后一个值是最大连接数
listen(socket_fd,10);
accept()
与客户端建立连接。建立失败,则返回-1。
该函数会起到一个阻塞作用,直到有客户端连接,该函数才执行通过,否则一直处于等待、阻塞状态
int client_fd = accept(socket_fd,(struct sockaddr *)&client_addr,&len)
read()/recv()
用来接收客户端消息,recv()比read()多一个参数,可以指定标志来控制如何接收数据,若为0,则二者一样。
二者的具体区别,这篇文章说的挺详细的:https://blog.csdn.net/hhhlizhao/article/details/73912578
int Recv = recv(client_fd,recvBuf,sizeof(recvBuf),0);
write()/send()
给客户端发送消息
二者区别,可以参考:https://blog.csdn.net/qq_40443457/article/details/104250998
https://blog.csdn.net/baidu_15547923/article/details/90206381
send(client_fd,message,sizeof(message),0);
int Write = write(client_fd,sendBuf,strlen(sendBuf));
close()
关闭客户端
close(client_fd);
close(socket_fd);
工程源码
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
using namespace std;
#define message "The server is ready!"
#define SendBuf "Server Received!"
int socket_fd;
int client_fd;
int queryCount = 1; //查询次数
char recvBuf[1024]={0}; //接收
int BuildSock()
{
//1.创建一个socket套接字
socket_fd = socket(AF_INET,SOCK_STREAM,0);
if (socket_fd == -1)
{
cout << "Socket Error!" << endl;
exit(-1);
}
cout << "Socket Ready!" << endl;
//2.sockaddr_in结构体:可以存储一套网络地址(包括IP与端口),此处存储本机IP地址与本地的一个端口
struct sockaddr_in local_addr;
local_addr.sin_family = AF_INET;
local_addr.sin_port = htons(端口号); //绑定端口
local_addr.sin_addr.s_addr = inet_addr("IP地址"); //绑定本机IP地址
//3.bind(): 将一个网络地址与一个套接字绑定,此处将本地地址绑定到一个套接字上
int Bind = bind(socket_fd, (struct sockaddr *)&local_addr, sizeof(local_addr)); //成功则返回0 ,失败返回-1
if (Bind == -1)
{
cout << "Bind Error!" << endl;
return -1;
}
cout << "Bind Ready!" << endl;
}
int ListenClient()
{
string strSendBuf;
struct sockaddr_in client_addr;
socklen_t len=sizeof(client_addr);
client_fd=accept(socket_fd,(struct sockaddr *)&client_addr,&len); //建立连接
if (client_fd == -1)
{
cout<<"accept错误\n"<<endl;
//exit(-1);
}
else
{
char *ip=inet_ntoa(client_addr.sin_addr);
cout<<"客户端:"<<ip<<" 连接到本服务器成功!"<<endl;
send(client_fd,message,sizeof(message),0); //发送初始化消息
while (true)
{
int Recv = recv(client_fd,recvBuf,sizeof(recvBuf),0); //接受来自客户端的内容
if (Recv<0)
{
cout<<"未接收到客户端的请求,或客户端已断开"<<endl;
return -1;
}
cout<<"*********************************"<<endl;
cout<<"查询次数:"<<queryCount<<endl;
cout<<"receive data: "<<recvBuf<<endl;
char sendBuf[99999]={0};
queryCount++;
cout<<"给客户端发送数据中..."<<endl;
int Write = write(client_fd,sendBuf,strlen(sendBuf));
if (Write<0)
{
cout << "Error: Send info to server failed !"<<endl;
return -1;
}
cout<<"给客户端发送完成!"<<endl;
cout<<"*********************************"<<endl;
}
}
close(client_fd);
}
int main(int argc,char *argv[])
{
cout<<"============================="<<endl;
cout<<" This is the server "<<endl;
cout<<"============================="<<endl;
BuildSock();
listen(socket_fd,10);
while (1)
{
cout<<"等待客户端的连接..."<<endl;
ListenClient();
}
close(socket_fd);
return 0;
}
二.Windows服务器端
引入头文件winsock2.h
WSAStartup()
打开网络库
WSADATA wsd;
WSAStartup(MAKEWORD(2, 2), &wsd);
socket()
创建socket套接字,和服务器端类似
int m_SockServer = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in结构体
创建一个sockaddr_in结构体,存储网络地址(IP、Port)
sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(端口号);
serveraddr.sin_addr.S_un.S_addr = inet_addr("IP地址");
bind()
绑定套接字
bind(m_SockServer, (sockaddr*)&serveraddr, sizeof(serveraddr));
listen()
设置监听
listen(m_SockServer, 0);
accept()
与客户端建立连接
int len = sizeof(serveraddr);
accept(m_SockServer, (sockaddr*)&serveraddrfrom, &len);
recv()
接收来自客户端的消息
recv(m_SockClient, buffer, 1024, 0);
send()
给客户端发送消息
send(m_Server[iConnect], buf, sizeof(buf), 0);
释放资源
WSACleanup();
注意:write()、send()、recv()、sizeof()和strlen()的使用区别
工程源码
server.cpp:
#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<thread>
#include<winsock2.h>
#include "server.h"
#include<assert.h>
#include<stdio.h>
#include<Windows.h>
#include<list>
#pragma comment(lib,"ws2_32.lib") //链接库文件
using namespace std
char Ip[20][200] = { '\0' };
int iConnect = 0; //当前客户端数量
int main(void)
{
WSADATA wsd; //定义WSADATA对象
if (WSAStartup(MAKEWORD(2, 2), &wsd))
{
printf("Initlalization Error!");
return -1;
}
sockaddr_in serveraddr; //创建sockaddr_in对象储存自身信息(当有多个端口,可以多个绑定)
serveraddr.sin_family = AF_INET; //设置服务器地址家族
serveraddr.sin_port = htons(端口号); //设置服务器端口号
serveraddr.sin_addr.S_un.S_addr = inet_addr("IP地址"); //本机既作为服务器有作为客户端的话,ip为127.0.0.1或localhost
//int socket(int domain, int type, int protocol);协议域 socket类型 指定协议0 auto:type
int m_SockServer; //创建socket对象映射 本质是int型 SOCKET
m_SockServer = socket(AF_INET, SOCK_STREAM, 0); //创建一个临时变量并赋值给m_SockServer
//std::cout << m_SockServer << std::endl;
//int bind( int sockfd, const struct sockaddr addr, socklen_t addrlen); sockfd为socket映射参数,唯一确定一个socket
int i = bind(m_SockServer, (sockaddr*)&serveraddr, sizeof(serveraddr)); //把映射和socket绑定
std::cout << "bind:" << i << std::endl;//0 表示成功
int iMaxConnect = 20; //最大连接数
char buf[] = "Server Connection Successful\0";
char WarnBuf[] = "It is voer Max connect\0";
sockaddr_in serveraddrfrom;
int m_Server[20]; //创建socket数组来存放来自客户端的信息最大连接数为20
while (true)
{
//int listen(int sockfd, int backlog);
//第一个参数即为要监听的socket套接字,第二个参数为相应socket可以排队的最大连接个数。
int iLisRet = listen(m_SockServer, 0); //进行监听
int temp = 0;
int Len = sizeof(serveraddrfrom);
int len = sizeof(serveraddr); //serveraddr所占的字节大小
//int accept(int sockfd, struct sockaddr addr, socklen_t addrlen);
//accept函数的第一个参数为服务器的socket描述字,第二个参数为指向struct sockaddr *的指针,用于返回客户端的协议地址,第三个参数为协议地址的长度。
m_Server[iConnect] = accept(m_SockServer, (sockaddr*)&serveraddrfrom, &len);
if (m_Server[iConnect] != INVALID_SOCKET) //INVALID_SOCKET表示无效
{
//int getsockname(int,struct sockaddr addr, *len) -1表示创建失败
if (getsockname(m_Server[iConnect], (struct sockaddr*)&serveraddrfrom, &Len) != -1)
{ // serveraddrfrom 接受到客户端的socket信息
printf("listen address = %s:%d\n", inet_ntoa(serveraddrfrom.sin_addr), ntohs(serveraddrfrom.sin_port));
printf(Ip[iConnect], "%s", inet_ntoa(serveraddrfrom.sin_addr));
}
else
{
printf("getsockname error\n");
exit(0);
}
//int send (int socket_id,char [],int lens,0);
int ires = send(m_Server[iConnect], buf, sizeof(buf), 0); //发送字符过去
std::cout << "accept" << ires << std::endl << std::endl; //显示已经连接次数
iConnect++;//当前连接数
if (iConnect > iMaxConnect)
{
//判断连接数是否大于最大连接数
int ires = send(m_Server[iConnect], WarnBuf, sizeof(WarnBuf), 0);
}
else
{
std::thread th1(test_threadpro, (void*)m_Server[--iConnect]); //启动线程
th1.detach();
}
}
}
WSACleanup(); //用于释放ws2_32.dll动态链接库初始化时分配的资源
}
三.客户端
在Windows环境下,引用头文件winsock2.h
WSAStartup()
1.打开网络库
WSADATA wsd;
WSAStartup(MAKEWORD(2, 2), &wsd);
2.socket()
创建socket套接字,和服务器端类似
SOCKET Socket;
Socket = socket(AF_INET, SOCK_STREAM, 0);
3.创建sockaddr_in结构体
创建一个sockaddr_in结构体,存储网络地址(IP、Port)
sockaddr_in SockAddr_In;
SockAddr_In.sin_family = AF_INET; //ipv4
SockAddr_In.sin_port = htons(端口号); //端口
SockAddr_In.sin_addr.S_un.S_addr = inet_addr("IP"); //目标服务器的IP
4.accept()
与服务器端建立连接,连接失败,返回-1
int i = accept(Socket, (sockaddr*) & SockAddr_In, sizeof(SockAddr_In));
此处接至服务器端accept()下
5.write()/send()
给服务器端发送消息
int Send = send(Socket, input, sizeof(input), 0);
注意:输入过程中,要注意:cin以空格为分隔,输入的字符串中有空格的时候,会被分开。使用cin.getline()
6.recv()/read()
接收来自服务器端的消息
receive = recv(Socket, recvbuf, sizeof(recvbuf), 0);
关闭客户端的时候,服务器端也会随之断开:注意close()的使用
7.工程源码
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <iostream>
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
int main()
{
WSADATA wsd;
WSAStartup(MAKEWORD(2, 2), &wsd);
SOCKET Socket;
sockaddr_in SockAddr_In;
SockAddr_In.sin_family = AF_INET; //ipv4
SockAddr_In.sin_port = htons(端口号); //端口
SockAddr_In.sin_addr.S_un.S_addr = inet_addr("IP"); //目标服务器的IP
Socket = socket(AF_INET, SOCK_STREAM, 0);
int i = connect(Socket, (sockaddr*) & SockAddr_In, sizeof(SockAddr_In)); //connect连接成功,返回0;失败返回SOCKET_ERROR
cout << "Connection status " << i << endl;
if (i == -1)
{
cout << "连接服务器失败!" << endl;
}
char recvbuf[99999]; //接收到服务器端的内容
int receive; //服务器端接收的
receive = recv(Socket, recvbuf, sizeof(recvbuf), 0);
if (receive > 0)
{
cout << "从服务器端开始接收:" << recvbuf << endl;
while (true)
{
char input[1024] = {0}; //键盘输入的字符
receive = 0;
cout << "发送给服务器端的内容:";
cin.getline(input, 1024); //cin以空格为分隔,输入的字符串中有空格的时候,会被分开
if (!strcmp(input,"exit")) //输入exit直接退出
{
send(Socket, input, sizeof(input), 0);
return 0;
}
int Send = send(Socket, input, sizeof(input), 0);
memset(recvbuf, '\0', sizeof(recvbuf));
//cout << receive << endl;
receive = recv(Socket, recvbuf, sizeof(recvbuf), 0);
if (receive >= 0)
{
cout << "从服务器端接收到的内容:\n" << recvbuf << endl;
}
}
}
}
整体运行效果图: