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("线程启动\n");
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]='\0';
27             printf("服务器收到消息:%s\n",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\n",inet_ntoa(remote_addr.sin_addr));
76         len=send(client_sockfd[count],"Welcome to my server\n",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]='\0';
19         printf("received:%s\n",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\n");
49     len=recv(client_sockfd,buf,BUFSIZ,0);//接收服务器端信息
50          buf[len]='\0';
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:\n");
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这两个函数完成。

猜你喜欢

转载自www.cnblogs.com/wzzgeorge/p/12020631.html