Windows下C语言实现 hello/hi 多线程网络聊天程序以及代码分析

早过忘川 提交于 2019-12-08 13:19:32

一、编译环境

  系统: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函数将消息发送给客户端。

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!