zoukankan      html  css  js  c++  java
  • C语言实现简易client/server网络多人聊天工具

    一、C语言实现一个简易的client/server聊天工具

      在ubuntu平台上,采用c语言实现一个简易的client/server聊天工具,思路是:

      服务器端:首先创建一个服务器进程,该进程监听客户端的连接,如果收到并建立连接后创建一个线程服务该客户端。该线程负责消息的转发(这里为了方便直接对消息进行广播)。

      客户端:客户端进程首先创建一个线程用于消息接收处理,然后为用户提供信息输入的交互界面。

      主要调用栈:

      int socket( int domain, int type, int protocol)

    • 功能:创建一个新的套接字,返回套接字描述符
    • 参数说明:
    • domain:域类型,指明使用的协议栈,如TCP/IP使用的是 PF_INET
    • type: 指明需要的服务类型, 如
    • SOCK_DGRAM: 数据报服务,UDP协议
    • SOCK_STREAM: 流服务,TCP协议
    • protocol:一般都取0
    • 举例:s=socket(PF_INET,SOCK_STREAM,0)

      int connect(int sockfd,struct sockaddr *server_addr,int sockaddr_len)

    • 功能: 同远程服务器建立主动连接,成功时返回0,若连接失败返回-1。
    • 参数说明:
    • Sockfd:套接字描述符,指明创建连接的套接字
    • Server_addr:指明远程端点:IP地址和端口号
    • sockaddr_len :地址长度

      int bind(int sockfd,struct sockaddr * my_addr,int addrlen)

    • 功能:为套接字指明一个本地端点地址TCP/IP协议使用sockaddr_in结构,包含IP地址和端口号,服务器使用它来指明熟知的端口号,然后等待连接
    • 参数说明:
    • Sockfd:套接字描述符,指明创建连接的套接字
    • my_addr:本地地址,IP地址和端口号
    • addrlen :地址长度
    • 举例:bind(sockfd, (struct sockaddr *)&address, sizeof(address));

      int listen(int sockfd,int input_queue_size)

    • 功能:
    • 面向连接的服务器使用它将一个套接字置为被动模式,并准备接收传入连接。用于服务器,指明某个套接字连接是被动的
    • 参数说明:
    • Sockfd:套接字描述符,指明创建连接的套接字
    • input_queue_size:该套接字使用的队列长度,指定在请求队列中允许的最大请求数
    • 举例:listen(sockfd,20)

      int accept(int sockfd, void *addr, int *addrlen);

    • 功能:获取传入连接请求,返回新的连接的套接字描述符。为每个新的连接请求创建了一个新的套接字,服务器只对新的连接使用该套接字,原来的监听套接字接受其他的连接请求。新的连接上传输数据使用新的套接字,使用完毕,服务器将关闭这个套接字。
    • 参数说明:
    • Sockfd:套接字描述符,指明正在监听的套接字
    • addr:提出连接请求的主机地址
    • addrlen:地址长度
    • 举例:new_sockfd = accept(sockfd, (struct sockaddr *)&address, &addrlen);

      int send(int sockfd, const void * data, int data_len, unsigned int flags)

    • 功能:在TCP连接上发送数据,返回成功传送数据的长度,出错时返回-1。send会将外发数据复制到OS内核中
    • 参数说明:
    • sockfd:套接字描述符
    • data:指向要发送数据的指针
    • data_len:数据长度
    • flags:一直为0
    • 举例(p50):send(s,req,strlen(req),0);

      int recv(int sockfd, void *buf, int buf_len,unsigned int flags);

    • 功能:从TCP接收数据,返回实际接收的数据长度,出错时返回-1。服务器使用其接收客户请求,客户使用它接受服务器的应答。如果没有数据,将阻塞,如果收到的数据大于缓存的大小,多余的数据将丢弃。
    • 参数说明:
    • Sockfd:套接字描述符
    • Buf:指向内存块的指针
    • Buf_len:内存块大小,以字节为单位
    • flags:一般为0
    • 举例:recv(sockfd,buf,8192,0)

      close(int sockfd);

    • 功能:撤销套接字。如果只有一个进程使用,立即终止连接并撤销该套接字,如果多个进程共享该套接字,将引用数减一,如果引用数降到零,则撤销它。
    • 参数说明:
    • Sockfd:套接字描述符
    • 举例:close(socket_descriptor)

    二、服务器端源代码:

     1 #include <stdio.h>
     2 #include <pthread.h>
     3 #include <string.h>
     4 #include <sys/types.h>
     5 #include <sys/socket.h>
     6 #include <netinet/in.h>
     7 #include <arpa/inet.h>
     8 #include <unistd.h> 
     9 
    10 int client_sockfd[100] = {0};//客户端套接字
    11 int count = 0;
    12 
    13 void* clientThreadLoop(void* local)
    14 {
    15     char buf[BUFSIZ];  //数据传送的缓冲区
    16 
    17     //printf("线程启动
    ");
    18     int socket_id = *(int *)local;
    19     while(1)
    20     {
    21         memset(buf,0x00,sizeof(buf));
    22         int len=recv(socket_id,buf,BUFSIZ,0);
    23         /*接收客户端的数据并将其发送给客户端--recv返回接收到的字节数,send返回发送的字节数*/
    24         if(len > 0)
    25         {
    26             buf[len]='';
    27             printf("服务器收到消息:%s
    ",buf);
    28             for(int i=0;i<count;i++)
    29             {
    30                 send(client_sockfd[i],buf,len,0);
    31             }
    32         }
    33     }
    34 }
    35 
    36 int main(int argc, char *argv[])
    37 {
    38     int server_sockfd;//服务器端套接字
    39     int len;
    40     struct sockaddr_in my_addr;   //服务器网络地址结构体
    41     struct sockaddr_in remote_addr = {0}; //客户端网络地址结构体
    42     int sin_size;
    43     memset(&my_addr,0,sizeof(my_addr)); //数据初始化--清零
    44     my_addr.sin_family=AF_INET; //设置为IP通信
    45     my_addr.sin_addr.s_addr=INADDR_ANY;//服务器IP地址--允许连接到所有本地地址上
    46     my_addr.sin_port=htons(8000); //服务器端口号
    47     
    48     /*创建服务器端套接字--IPv4协议,面向连接通信,TCP协议*/
    49     if((server_sockfd=socket(PF_INET,SOCK_STREAM,0))<0)
    50     {  
    51         perror("socket");
    52         return 1;
    53     }
    54  
    55         /*将套接字绑定到服务器的网络地址上*/
    56     if (bind(server_sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))<0)
    57     {
    58         perror("bind");
    59         return 1;
    60     }
    61     
    62     /*监听连接请求--监听队列长度为5*/
    63     listen(server_sockfd,5);
    64     
    65     sin_size=sizeof(struct sockaddr_in);
    66     while(1)
    67     {
    68         /*等待客户端连接请求到达*/
    69         if((client_sockfd[count]=accept(server_sockfd,(struct sockaddr *)&remote_addr,&sin_size))<0)
    70         {
    71             perror("accept");
    72             return 1;
    73         }
    74         pthread_t tid;
    75         printf("accept client %s
    ",inet_ntoa(remote_addr.sin_addr));
    76         len=send(client_sockfd[count],"Welcome to my server
    ",21,0);//发送欢迎信息
    77         pthread_create(&tid,NULL,clientThreadLoop,&client_sockfd[count]);
    78         count++;
    79     }
    80 
    81     for(int j=0;j<count;j++)
    82         close(client_sockfd[j]);
    83     close(server_sockfd);
    84     return 0;
    85 }

      调用accept()后进程阻塞等待,当连接成功后返回的套接字存入client_sockfd[count]数组,通过调用pthread_create()创建线程。

    三、客户端源代码:

     1 #include <stdio.h>
     2 #include <pthread.h>
     3 #include <string.h>
     4 #include <sys/types.h>
     5 #include <sys/socket.h>
     6 #include <netinet/in.h>
     7 #include <arpa/inet.h>
     8 #include <unistd.h> 
     9 
    10 void* recThreadLoop(void* id)
    11 {
    12     char buffer[BUFSIZ];
    13     int cur_id = *(int *)id;
    14     while(1)
    15     {
    16         memset(buffer,0x00,sizeof(buffer));
    17         int len=recv(cur_id,buffer,BUFSIZ,0);
    18         buffer[len]='';
    19         printf("received:%s
    ",buffer);
    20     }
    21 }
    22 
    23 int main(int argc, char *argv[])
    24 {
    25     pthread_t tid;
    26     int client_sockfd;
    27     int len;
    28     struct sockaddr_in remote_addr; //服务器端网络地址结构体
    29     char buf[BUFSIZ];  //数据传送的缓冲区
    30     memset(&remote_addr,0,sizeof(remote_addr)); //数据初始化--清零
    31     remote_addr.sin_family=AF_INET; //设置为IP通信
    32     remote_addr.sin_addr.s_addr=inet_addr("127.0.0.1");//服务器IP地址
    33     remote_addr.sin_port=htons(8000); //服务器端口号
    34     
    35     /*创建客户端套接字--IPv4协议,面向连接通信,TCP协议*/
    36     if((client_sockfd=socket(PF_INET,SOCK_STREAM,0))<0)
    37     {
    38         perror("socket");
    39         return 1;
    40     }
    41     
    42     /*将套接字绑定到服务器的网络地址上*/
    43     if(connect(client_sockfd,(struct sockaddr *)&remote_addr,sizeof(struct sockaddr))<0)
    44     {
    45         perror("connect");
    46         return 1;
    47     }
    48     printf("connected to server
    ");
    49     len=recv(client_sockfd,buf,BUFSIZ,0);//接收服务器端信息
    50          buf[len]='';
    51     printf("%s",buf); //打印服务器端信息
    52     
    53     pthread_create(&tid,NULL,recThreadLoop,&client_sockfd);
    54 
    55     /*循环的发送接收信息并打印接收信息--recv返回接收到的字节数,send返回发送的字节数*/
    56     while(1)
    57     {
    58         printf("Enter string to send:
    ");
    59         scanf("%s",buf);
    60         if(!strcmp(buf,"quit"))
    61             break;
    62         len=send(client_sockfd,buf,strlen(buf),0);
    63     }
    64     close(client_sockfd);//关闭套接字
    65     return 0;
    66 }

    四、浏览器打开一个URL网址进行的步骤如下:

      在DNS解析阶段,需要用到如下调用栈:

    • gethostname 获得主机名
    • getpeername 获得与套接口相连的远程协议地址
    • getsockname 获得套接口本地协议地址
    • gethostbyname 根据主机名取得主机信息
    • gethostbyaddr 根据主机地址取得主机信息
    • getprotobyname 根据协议名取得主机协议信息
    • getprotobynumber 根据协议号取得主机协议信息
    • getservbyname 根据服务名取得相关服务信息
    • getservbyport 根据端口号取得相关服务信息
    • getsockopt/setsockopt 获取/设置一个套接口选项
    • ioctlsocket 设置套接口的工作方式

      获取到域名对应的IP之后便可以开始向服务器请求连接。

    五、Socket系统调用

      Socket系统调用的流程:

        (1)系统调用 –> (2)查找socket –> (3)执行socket的对应操作函数 –> (4)执行传输层协议的对应操作函数;

      内核源码:

     1 SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
     2 {
     3     int retval;
     4     struct socket *sock;
     5     int flags;
     6 
     7 ...
     8     retval = sock_create(family, type, protocol, &sock);
     9     if (retval < 0)
    10         goto out;
    11 
    12     retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
    13     if (retval < 0)
    14         goto out_release;
    15 
    16 out:
    17     /* It may be already another descriptor 8) Not kernel problem. */
    18     return retval;
    19 
    20 out_release:
    21     sock_release(sock);
    22     return retval;
    23 }

      可以看到socket函数主要由sock_create和sock_map_fd这两个函数完成。

  • 相关阅读:
    [CSP-S模拟测试]:迷宫(最短路)
    [CSP-S模拟测试]:五子棋(模拟)
    [CSP-S模拟测试]:点亮(状压DP+树上背包DP)
    [CSP-S模拟测试]:统计(树状数组+乱搞)
    [CSP-S模拟测试]:组合(欧拉路)
    [CSP-S模拟测试]:笨小猴(随机化)
    最小表示法
    BZOJ4868 [Shoi2017]期末考试 【三分 + 贪心】
    BZOJ4870 [Shoi2017]组合数问题 【组合数 + 矩乘】
    BZOJ4919 [Lydsy1706月赛]大根堆 【dp + 启发式合并】
  • 原文地址:https://www.cnblogs.com/wzzgeorge/p/12020631.html
Copyright © 2011-2022 走看看