对于socket通讯,网上有很多教程,我就简单的说一下socket通讯步骤,便于理解下面的代码。
服务器端:
首先,应该初始化套接字库,初始化套接字socket()
然后,绑定socket套接字到一个端口上,bind()
接着,将套接字设为监听状态listen()
此时,可以用accept()接受连接请求,它也会返回一个socket套接字,这是对每个连接的套接字。
现在,可以使用rec(),send()进行消息的收发
最后,结束连接,关闭套接字closesocket(),对于当前连接的套接字和服务端的总套接字要分别关闭。清理套接字库。
客户端:
加载套接字库,创建套接字socket();
向服务器发出连接请求connect();
和服务器端进行通信send()/recv();
关闭套接字closesocket(),关闭加载的套接字库。
网络通信中,大多数情况应该有多个客户端,多个客户端同时连接服务端。可以有多种解决方案,比如IOCP模型。
这里我采用最简单的创建多线程,为每个连接创建一个线程,结束连接时关闭此线程。
按照网上说法,创建线程最好使用_beginthreadex而不要使用createthread。
服务端代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
#include <stdio.h> #include <winsock2.h> #include <process.h> #include <windows.h> #include <assert.h> #pragma comment(lib,"ws2_32.lib") //参数结构体,用来把多个参数传入线程函数, struct args{ SOCKET sClient; //当前连接的socket套接字 sockaddr_in remoteAddr; //当前连接的端口号 }; //每个连接的线程 unsigned int __stdcall createlink(void* arg){ args ap = *((args*)arg); //这里将传入的无类型指针转换成args*类型,再取其值所表示的结构体,赋值给ap printf("接受到一个连接:%s:%d\r\n", inet_ntoa(ap.remoteAddr.sin_addr), ap.remoteAddr.sin_port); char revData[255] = {}; while (1) { //接收数据 int ret = recv(ap.sClient, revData, 255, 0); if(ret > 0) { revData[ret] = 0x00; printf(revData); if (revData[0] == '#') { printf("END\n"); break; //如果接收到的字符串首字符为'#',跳出收发消息循环 } } //发送数据 //char * sendData = "服务端:你好,TCP客户端!\n"; char sendData[20]; sprintf(sendData,"ser: hello! %d!\n",ap.remoteAddr.sin_port); send(ap.sClient, sendData, strlen(sendData), 0); } closesocket(ap.sClient); //结束连接前关闭当前连接的套接字 return 0; } int main(int argc, char* argv[]) { //初始化WSA WORD sockVersion = MAKEWORD(2,2); WSADATA wsaData; if(WSAStartup(sockVersion, &wsaData)!=0) { return 0; } //创建套接字 SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if(slisten == INVALID_SOCKET) { printf("socket error !"); return 0; } //绑定IP和端口 sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(8888); sin.sin_addr.S_un.S_addr = INADDR_ANY; if(bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR) { printf("bind error !"); } //开始监听 if(listen(slisten, 5) == SOCKET_ERROR) { printf("listen error !"); return 0; } //循环接收数据 printf("等待连接...\n"); int thrdnum = 0; HANDLE handle[5]; //连接线程数组,最大5个连接 while(1){ //accept()获得接入连接,返回连接的套接字 sockaddr_in remoteAddr; int nAddrlen = sizeof(remoteAddr); SOCKET sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen); if(sClient == INVALID_SOCKET) { printf("accept error !"); } args arg; arg.sClient = sClient; arg.remoteAddr = remoteAddr; //为连接创建线程,传入连接套接字,端口号 handle[thrdnum++] = (HANDLE)_beginthreadex(NULL, 0, createlink, &arg, 0, NULL); } WaitForMultipleObjects(thrdnum, handle, TRUE, INFINITE); //等待所有线程都结束 closesocket(slisten); //关闭服务端的套接字 WSACleanup(); getchar(); return 0; } |
这里有一处需要特别注意,就是把args结构体作为参数传入线程参数,这里有两种实现:
一种是我现在使用的方法,在main中,while(1)表示一直等待连接接入,每次连接,就会把sClient结构体指针传进去。这里传进去的是一个void * 类型,传进去后转换成args*类型,再把内容赋值给新的args结构体ap。
另一种办法是把 main中声明args arg改成args arg = new args;然后传入arg,这样相当于为每个连接new一个结构体,不过要记得在结束连接时释放内存。
我之前采用了传的结构体指针,内部却忘了把它赋值给一个新的结构体,导致每次连接的信息会覆盖旧连接,导致旧连接卡死。
目前的程序没有写如何结束服务端的死循环,暂时就这样吧。
客户端代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
#include <cstdio> #include <winsock2.h> #include<ctype.h> //#pragma comment(lib,"ws2_32.lib") int communicate(SOCKET sclient) { //通讯前进行一次应答,获得端口信息 char remote_num[7] = {}; int digit_locate =0; char recData[255] = {}; int ret = recv(sclient, recData, 255, 0); if(ret > 0) { recData[ret] = 0x00; printf(recData); for(int i = 0 ;recData[i] != ''; i++) if(isdigit(recData[i])) remote_num[digit_locate++]=recData[i]; } char inputd[200] = {}; //进入收发循环 while(1) { char sendData[255] = {}; sprintf(sendData,"%s: %s!\n", remote_num, inputd); //发送信息 send(sclient, sendData, strlen(sendData), 0); printf(sendData); //接收信息 int ret = recv(sclient, recData, 255, 0); if(ret > 0) { recData[ret] = 0x00; printf(recData); } printf("please input: "); scanf("%s",inputd); //如果输入的首字母为'#'或'*',跳出循环 if(inputd[0] == '#' || inputd[0] == '*') break; } send(sclient, inputd, strlen(inputd), 0); return 0; } int main(int argc, char* argv[]) { //初始化套接字 WORD sockVersion = MAKEWORD(2,2); WSADATA data; if(WSAStartup(sockVersion, &data) != 0) { return 0; } sockaddr_in serAddr; serAddr.sin_family = AF_INET; serAddr.sin_port = htons(8888); serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if(sclient == INVALID_SOCKET) { printf("invalid socket !"); return 0; } //连接服务器 if (connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR) { printf("connect error !"); closesocket(sclient); return 0; } //进行通讯函数 communicate(sclient); //关闭套接字,结束客户端 closesocket(sclient); WSACleanup(); printf("END\n"); getchar(); return 0; } |