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这两个函数完成。

  • 相关阅读:
    Service Name Port Number Transport Protocol tcp udp 端口号16bit
    linux linux 互传文件 win 不通过 ftp sftp 往linux 传文件(文件夹)
    soft deletion Google SRE 保障数据完整性的手段
    Taylor series
    Taylor's theorem
    Moving average
    REQUEST
    Unix file types
    mysqld.sock
    Tunneling protocol
  • 原文地址:https://www.cnblogs.com/wzzgeorge/p/12020631.html
Copyright © 2011-2022 走看看