本实验是在ubuntu系统下实现一个简单的客户端和服务端通信的简单demo,以TCP协议为例。
首先介绍API:
server用到的API有socket,bind,listen,accept,read,write,close (read和write可以用send和recv替换)
client用到的API有socket,connect,read,write,close (read和write可以用send和recv替换)
下面来看一下各个API的声明和描述,用man查看各个api声明:
因为linux系统把一切都当做文件来看待,所以像操作文件一样来操作socket文件描述符,写入数据就是发送数据,读数据就是接收数据,这种做法符合linux系统的一切都当做文件来看待的设计理念。当然也可以用send和recv函数,read和write底层调用的跟send和recv的实现是一样的。
上面就是各种API的介绍,总结来说;
socket函数主要是来定义是使用流数据还是数据报数据,对应的就是TCP和UDP协议,TCP的负载对传输层来说是没有数据结构的概念的,对于上层调用write函数,只是将数据放到发送缓冲区,每次write就只是把数据放到发送缓冲区的最后,就是排队放在后面,完全没有结构的概念,没有一次write是属于一个报文,而UDP就是一次发送就是一个报文,接收只能一个报文一个报文的接收。假如,存在一个客户端连续向服务端连续发送10次后才接收,TCP将会接收到所有数据,而UDP只会一个一个报文的接收,这就是流和数据报的区别。对应客户端来说,socket函数返回的是与服务端绑定的终结点文件描述符。
bind函数就是绑定一个IP和端口号,IP对应主机,不想设置IP的就设置INADDR_ANY,这会默认绑定本机的所有IP,IP就是对应主机,能找到主机就可以了,而端口号对应的特定的应用程序。
listen函数是设置最大等待连接的个数,超过设置的握手请求将会被reset。
accpet函数就是等待客户端连接,对应的是客户端的connect函数,这个过程就是三报文握手的过程,确定双发的收发能力正常。accept函数返回的是和客户端绑定的终结点文件描述符。
wirte和read函数就是发送和接收数据,write和read只是和内核交换数据,write函数是将数据拷贝到发送缓冲区,read函数是将内核的接收缓冲区拷贝到用户缓冲区。
close函数就四报文挥手过程。
下面是demo,代码很简单
server.c
1 #include <stdio.h> 2 #include <sys/types.h> 3 #include <sys/socket.h> 4 #include <unistd.h> 5 #include <string.h> 6 #include <netinet/in.h> 7 #include <pthread.h> 8 #include <stdlib.h> 9 void *fun(void *arg) 10 { 11 int fd; 12 char sendbuf[64]; 13 fd = *(int *)arg; 14 printf("Please input:\n"); 15 while(1) 16 { 17 memset(sendbuf, 0, sizeof(sendbuf)); 18 scanf("%s", sendbuf); 19 write(fd, sendbuf, strlen(sendbuf)); 20 if(!strncmp(sendbuf, "quit", strlen("quit"))) 21 { 22 close(fd); 23 exit(0); 24 } 25 } 26 return NULL; 27 } 28 29 int main() 30 { 31 int socket_fd, client_fd; 32 int ret, len; 33 char recvbuf[64]; 34 struct sockaddr_in server_addr, client_addr; 35 pthread_t tid; 36 socket_fd = socket(PF_INET, SOCK_STREAM, 0); 37 38 if(socket_fd < 0) 39 { 40 perror("Error"); 41 return -1; 42 } 43 44 server_addr.sin_family = AF_INET; 45 server_addr.sin_addr.s_addr = INADDR_ANY; 46 server_addr.sin_port = htons(33333); 47 48 ret = bind(socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)); 49 if(ret < 0) 50 { 51 perror("Error"); 52 return -1; 53 } 54 55 listen(socket_fd, 5); 56 57 len = sizeof(client_addr); 58 client_fd = accept(socket_fd, (struct sockaddr *)&client_addr, &len); 59 if(client_fd < 0) 60 { 61 perror("Error"); 62 return -1; 63 } 64 pthread_create(&tid, NULL, fun, (void *)&client_fd); 65 while(1) 66 { 67 memset(recvbuf, 0, sizeof(recvbuf)); 68 read(client_fd, recvbuf, sizeof(recvbuf)); 69 if(!strncmp(recvbuf, "quit", strlen("quit"))) 70 { 71 pthread_cancel(tid); 72 break; 73 } 74 printf("recv: %s\n", recvbuf); 75 } 76 77 close(client_fd); 78 close(socket_fd); 79 return 0; 80 }
client.c
1 #include <stdio.h> 2 #include <sys/types.h> 3 #include <sys/socket.h> 4 #include <netinet/in.h> 5 #include <unistd.h> 6 #include <string.h> 7 #include <pthread.h> 8 #include <stdlib.h> 9 void *fun(void *arg) 10 { 11 int fd; 12 char sendbuf[64]; 13 fd = *(int *)arg; 14 printf("Please input:\n"); 15 while(1) 16 { 17 memset(sendbuf, 0, sizeof(sendbuf)); 18 scanf("%s", sendbuf); 19 write(fd, sendbuf, strlen(sendbuf)); 20 if(!strncmp(sendbuf, "quit", strlen("quit"))) 21 { 22 close(fd); 23 exit(0); 24 } 25 } 26 return NULL; 27 } 28 29 int main() 30 { 31 int socket_fd; 32 struct sockaddr_in server_addr; 33 char recvbuf[64]; 34 int ret; 35 pthread_t tid; 36 37 socket_fd = socket(PF_INET, SOCK_STREAM, 0); 38 if(socket_fd < 0) 39 { 40 perror("Error"); 41 return -1; 42 } 43 44 server_addr.sin_family = AF_INET; 45 server_addr.sin_port = htons(33333); 46 server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); 47 48 ret = connect(socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)); 49 if(ret < 0) 50 { 51 perror("Error"); 52 return -1; 53 } 54 55 ret = pthread_create(&tid, NULL, fun, (void *)&socket_fd); 56 if(ret != 0) 57 { 58 perror("Error"); 59 return -1; 60 } 61 while(1) 62 { 63 memset(recvbuf, 0, sizeof(recvbuf)); 64 read(socket_fd, recvbuf, sizeof(recvbuf)); 65 if(!strncmp(recvbuf, "quit", strlen("quit"))) 66 { 67 pthread_cancel(tid); 68 break; 69 } 70 printf("recv: %s\n", recvbuf); 71 } 72 close(socket_fd); 73 return 0; 74 }
客户端和服务端都创建了一个线程用于向对方发送数据,主线程用来接收对方发来的数据。这样可以同时发送数据和接收数据。
当输入“quit”,退出。下面是演示效果:
以上是这次实验的内容,由于本人水平有限,如有错误之处,还请指正,谢谢!