一、编译环境
系统:Windows 10 软件:CodeBlocks 17.12
二、完整代码
server:
1 #include <stdio.h> 2 #include <windows.h> 3 #include <Winsock2.h> 4 #include <string.h> 5 #include <pthread.h> 6 7 #pragma comment("ws2_32.lib") 8 9 struct mes{ 10 SOCKET clisock; 11 SOCKADDR_IN cliaddr; 12 }; 13 14 void* thread_new(void *); 15 16 int main() 17 { 18 WORD wVersionRequested; 19 WSADATA wsaData; 20 wVersionRequested = MAKEWORD(2,2); 21 if(WSAStartup(wVersionRequested, &wsaData) != 0) 22 { 23 printf("WSAStarup Failed!\n");//初始化错误 24 exit(-1); 25 } 26 if(wsaData.wVersion != wVersionRequested) 27 { 28 printf("The version of Winsock is not suited!\n");//winsock版本不匹配 29 WSACleanup();//结束调用 30 exit(-1); 31 } 32 //创建套接字 33 SOCKET servsock; 34 servsock = socket(AF_INET,SOCK_STREAM,0);//创建套接字,AF_INET代表IP家族,0是默认的方式创建 有连接是流式 无连接是数据包套接字 35 36 //定义服务器地址结构 37 SOCKADDR_IN tcpaddr; 38 tcpaddr.sin_family = AF_INET;//地址族(指定地址格式) 39 tcpaddr.sin_port = htons(5050);//指定端口号 40 tcpaddr.sin_addr.s_addr = inet_addr("127.0.0.1");//将一个点分十进制IP地址字符串转换成32位数字表示的IP地址 41 //绑定套接字到服务器地质结构 42 printf("Binding...\n"); 43 44 int iSockErr = bind(servsock, (SOCKADDR*)&tcpaddr, sizeof(SOCKADDR)); 45 if(iSockErr == SOCKET_ERROR) 46 { 47 WSAGetLastError();//根据不同的错误类型进行不同的处理 48 exit(0); 49 } 50 //监听套接字 51 printf("Listening...\n"); 52 iSockErr = listen(servsock, 5);//5代表可以连接的客户端数量,队列大小 53 if(iSockErr != 0) 54 { 55 printf("Listen Failed:%d\n",WSAGetLastError()); 56 exit(0); 57 } 58 //等待连接请求 59 int len = 0; 60 printf("Waiting TCP Request...\n"); 61 while(1) 62 { 63 SOCKET clisock; 64 SOCKADDR_IN cliaddr; 65 len = sizeof(cliaddr); 66 clisock = accept(servsock,(struct sockaddr *)&cliaddr,&len); 67 printf("Accpet TCP Client:%s:%d\n",inet_ntoa(cliaddr.sin_addr),htons(cliaddr.sin_port)); 68 char buff[256]; 69 sprintf(buff,"Welcome you %s",inet_ntoa(cliaddr.sin_addr)); 70 send(clisock,buff,strlen(buff),0); 71 pthread_t thrd; 72 struct mes m; 73 m.clisock = clisock; 74 m.cliaddr = cliaddr; 75 pthread_create(&thrd,NULL,thread_new,(void *)&m);// 76 77 } 78 79 //关闭连接 80 shutdown(servsock,2); 81 closesocket(servsock); 82 WSACleanup(); 83 return 0; 84 } 85 void* thread_new(void *d) 86 { 87 struct mes* m = (struct mes*)d; 88 SOCKET clisock = m->clisock; 89 SOCKADDR_IN cliaddr = m->cliaddr; 90 while(true) 91 { 92 char buffer[1024]; 93 if (recv(clisock,buffer,sizeof buffer,0)!=SOCKET_ERROR) 94 { 95 printf("Received datagram from TCP Client %s--%s\n",inet_ntoa(cliaddr.sin_addr),buffer); 96 ////给cilent发信息 97 strcpy(buffer,"Hi!"); 98 send(clisock,buffer,sizeof buffer,0); 99 100 } 101 else 102 { 103 printf("Disconnect TCP Client:%s:%d\n",inet_ntoa(cliaddr.sin_addr),htons(cliaddr.sin_port)); 104 break; 105 } 106 Sleep(500); 107 } 108 shutdown(clisock,2); 109 closesocket(clisock); 110 }
client:
1 #include <stdio.h> 2 #include <windows.h> 3 #include <Winsock2.h> 4 #include <string.h> 5 6 #pragma comment("ws2_32.lib") 7 8 int main() 9 { 10 WORD wVersionRequested; 11 WSADATA wsaData; 12 wVersionRequested = MAKEWORD(2,2); 13 if(WSAStartup(wVersionRequested, &wsaData) != 0) 14 { 15 printf("WSAStarup Failed!\n");//初始化错误 16 exit(-1); 17 } 18 if(wsaData.wVersion != wVersionRequested) 19 { 20 printf("The version of Winsock is not suited!\n");//winsock版本不匹配 21 WSACleanup();//结束调用 22 exit(-1); 23 } 24 //创建套接字 25 SOCKET clisock; 26 clisock = socket(AF_INET,SOCK_STREAM,0);//创建套接字,AF_INET代表IP家族,0是默认的方式创建 有连接是流式 无连接是数据包套接字 27 28 //定义服务器地址结构 29 SOCKADDR_IN tcpaddr; 30 tcpaddr.sin_family = AF_INET;//地址族(指定地址格式) 31 tcpaddr.sin_port = htons(5050);//指定端口号 32 tcpaddr.sin_addr.s_addr = inet_addr("127.0.0.1");//将一个点分十进制IP地址字符串转换成32位数字表示的IP地址 33 34 int iSockErr = connect(clisock, (SOCKADDR*)&tcpaddr, sizeof(SOCKADDR)); 35 if(iSockErr == SOCKET_ERROR) 36 { 37 printf("connect unsccessful!\n"); 38 iSockErr = closesocket(clisock); 39 if (iSockErr == SOCKET_ERROR) 40 printf("closesocket unsccessful!\n"); 41 exit(0); 42 } 43 char sendbuf[1024]; 44 char recvbuf[1024]; 45 while(true) 46 { 47 recv(clisock,recvbuf,sizeof(recvbuf),0); 48 printf("Received datagram from TCP server:%s\n",recvbuf); 49 printf("input message\n"); 50 scanf("%s",sendbuf); 51 if(strcmp(sendbuf,"q")==0) 52 break; 53 send(clisock,sendbuf,sizeof sendbuf,0)!=SOCKET_ERROR; 54 } 55 //关闭连接 56 shutdown(clisock,2); 57 closesocket(clisock); 58 WSACleanup(); 59 return 0; 60 }
三、代码分析
1、成果演示:
服务端:
客户端:
2、实验原理:
当我们使用不同的协议进行通信时就得使用不同的接口,还得处理不同协议的各种细节,这就增加了开发的难度,软件也不易于扩展。于是UNIX BSD就发明了socket这种东西,socket屏蔽了各个协议的通信细节,使得程序员无需关注协议本身,直接使用socket提供的接口来进行互联的不同主机间的进程的通信。这就好比操作系统给我们提供了使用底层硬件功能的系统调用,通过系统调用我们可以方便的使用磁盘(文件操作),使用内存,而无需自己去进行磁盘读写,内存管理。socket其实也是一样的东西,就是提供了tcp/ip协议的抽象,对外提供了一套接口,同过这个接口就可以统一、方便的使用tcp/ip协议的功能了。Socket所处层次如下图所示:
3、服务端开发流程:
第一步:启动Winsock
WORD wVersionRequested; WSADATA wsaData; wVersionRequested = MAKEWORD(2,2); if(WSAStartup(wVersionRequested, &wsaData) != 0) { printf("WSAStarup Failed!\n");//初始化错误 exit(-1); } if(wsaData.wVersion != wVersionRequested) { printf("The version of Winsock is not suited!\n");//winsock版本不匹配 WSACleanup();//结束调用 exit(-1); } |
关键代码分析:
wVersionRequested是一个WORD型(双字节型)数值,指定使用的版本号,对Winsock2.2而言,此参数的值为0x0202,也可以用宏MAKEWORD(2,2)来获得;
WSAStartup的第二个参数将会修改wsaData数据,返回关于Winsock实现的详细信息。
最后一个if语句用来判断启动的winsock与设置的winsock版本是否一致,如果不一致就调用WSACleanup函数结束调用。
第二步:创建套接字
//创建套接字 SOCKET servsock; servsock = socket(AF_INET,SOCK_STREAM,0); |
关键代码分析:
对于socket函数,用来创建套接字。其中参数AF_INET代表IP家族,0是默认的方式创建,SOCK_STREAM代表有连接是流式,也就是使用TCP协议。
第三步:定义服务器结构
SOCKADDR_IN tcpaddr; tcpaddr.sin_family = AF_INET;//地址族(指定地址格式) tcpaddr.sin_port = htons(5050);//指定端口号 tcpaddr.sin_addr.s_addr = inet_addr("127.0.0.1");//将一个点分十进制IP地址字符串转换成32位数字表示的IP地址 |
第四步:绑定套接字到服务器地址结构
int iSockErr = bind(servsock, (SOCKADDR*)&tcpaddr, sizeof(SOCKADDR)); if(iSockErr == SOCKET_ERROR) { WSAGetLastError();//根据不同的错误类型进行不同的处理 exit(0); } |
关键代码分析:
Bind函数将套接字sersock与服务器地址结构tcpaddr绑定。
第五步:监听套接字
printf("Listening...\n"); iSockErr = listen(servsock, 5);//5代表可以连接的客户端数量,队列大小 if(iSockErr != 0) { printf("Listen Failed:%d\n",WSAGetLastError()); exit(0); } |
关键代码分析:
使用listen函数对套接字sersock进行监听,第二个参数5代表可以连接的客户端数量。
第六步:收到消息后创建线程与客户端通话
//等待连接请求 int len = 0; printf("Waiting TCP Request...\n"); while(1) { SOCKET clisock; SOCKADDR_IN cliaddr; len = sizeof(cliaddr); clisock = accept(servsock,(struct sockaddr *)&cliaddr,&len); printf("Accpet TCP Client:%s:%d\n", inet_ntoa(cliaddr.sin_addr),htons(cliaddr.sin_port)); char buff[256]; sprintf(buff,"Welcome you %s",inet_ntoa(cliaddr.sin_addr)); send(clisock,buff,strlen(buff),0); //创建线程 pthread_t thrd; struct mes m; m.clisock = clisock; m.cliaddr = cliaddr; pthread_create(&thrd,NULL,thread_new,(void *)&m); } |
关键代码分析:
服务器端循环接受消息,不过会卡在accept函数,知道收到某个消息。
Accept函数会将客户端的地址结构赋值给定义的cliaddr,从中可以知道客户端的IP地址,端口等信息;并且会返回客户端的套接字。
在accept收到客户端的连接后,会先回馈一条消息“Welcome you …”,然后创建线程thrd,并且将线程运行需要的参数打包成一个结构体传给线程执行的函数thread_new。
使用pthread_create函数创建线程,第一个参数为声明的线程变量,第二个参数直接设置为NULL即可,第三个参数为线程执行的函数thread_new,第四个参数为函数thread_new所需要的参数。
第七步:实现线程的函数thread_new
void* thread_new(void *d) { struct mes* m = (struct mes*)d; SOCKET clisock = m->clisock; SOCKADDR_IN cliaddr = m->cliaddr; while(true) { char buffer[1024]; if (recv(clisock,buffer,sizeof buffer,0)!=SOCKET_ERROR) { printf("Received datagram from TCP Client %s--%s\n", inet_ntoa(cliaddr.sin_addr),buffer); //给cilent发信息 strcpy(buffer,"Hi!"); send(clisock,buffer,sizeof buffer,0);
} else { printf("Disconnect TCP Client:%s:%d\n", inet_ntoa(cliaddr.sin_addr),htons(cliaddr.sin_port)); break; } Sleep(500); } shutdown(clisock,2); closesocket(clisock); } |
关键代码分析:
使用recv函数从客户端收取消息
将Hi!字符串赋值给回送消息。
如果客户端断开连接,那么recv返回SOCKET_ERROR,将会进入分支else,打印该客户端断开连接的消息,并且结束循环,跳出while。关闭相关的套接字。
4、客户端开发流程:
第一步:启动Winsock
WORD wVersionRequested; WSADATA wsaData; wVersionRequested = MAKEWORD(2,2); if(WSAStartup(wVersionRequested, &wsaData) != 0) { printf("WSAStarup Failed!\n");//初始化错误 exit(-1); } if(wsaData.wVersion != wVersionRequested) { printf("The version of Winsock is not suited!\n");//winsock版本不匹配 WSACleanup();//结束调用 exit(-1); } |
关键代码分析:
wVersionRequested是一个WORD型(双字节型)数值,指定使用的版本号,对Winsock2.2而言,此参数的值为0x0202,也可以用宏MAKEWORD(2,2)来获得;
WSAStartup的第二个参数将会修改wsaData数据,返回关于Winsock实现的详细信息。
最后一个if语句用来判断启动的winsock与设置的winsock版本是否一致,如果不一致就调用WSACleanup函数结束调用。
第二步:创建套接字
//创建套接字 SOCKET servsock; servsock = socket(AF_INET,SOCK_STREAM,0); |
关键代码分析:
对于socket函数,用来创建套接字。其中参数AF_INET代表IP家族,0是默认的方式创建,SOCK_STREAM代表有连接是流式,也就是使用TCP协议。
第三步:定义服务器结构
SOCKADDR_IN tcpaddr; tcpaddr.sin_family = AF_INET;//地址族(指定地址格式) tcpaddr.sin_port = htons(5050);//指定端口号 tcpaddr.sin_addr.s_addr = inet_addr("127.0.0.1");//将一个点分十进制IP地址字符串转换成32位数字表示的IP地址 |
第四步:连接至服务器
int iSockErr = connect(clisock, (SOCKADDR*)&tcpaddr, sizeof(SOCKADDR)); if(iSockErr == SOCKET_ERROR) { printf("connect unsccessful!\n"); iSockErr = closesocket(clisock); if (iSockErr == SOCKET_ERROR) printf("closesocket unsccessful!\n"); exit(0); } |
关键代码分析:
connect函数将根据服务器地址结构连接。
第五步:从服务端接收消息
char sendbuf[1024]; char recvbuf[1024]; while(true) { recv(clisock,recvbuf,sizeof(recvbuf),0); printf("Received datagram from TCP server:%s\n",recvbuf); printf("input message\n"); scanf("%s",sendbuf); if(strcmp(sendbuf,"q")==0) break; send(clisock,sendbuf,sizeof sendbuf,0)!=SOCKET_ERROR; } |
关键代码分析:
使用recv函数从服务端收取消息;
使用send函数将消息发送给客户端。