本文主要是网络编程——TCP协议下的socket编程的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
Windows windows11 Ubuntu Ubuntu16.04的64位版本 VMware® Workstation 16 Pro 16.2.3 build-19376536 SecureCRT Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日 开发板 正点原子 i.MX6ULL Linux阿尔法开发板 uboot NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03) linux内核 linux-4.15(NXP官方提供) STM32开发板 正点原子战舰V3(STM32F103ZET6)
点击查看本文参考资料
点击查看相关文件下载
一、TCP
编程流程 1. TCP
编程步骤 首先我们需要知道使用TCP
编程时的基本步骤:
(1)调用socket()
函数打开套接字,得到套接字描述符;
(2)调用bind()
函数将套接字与IP
地址、端口号进行绑定(这一步也可以没有);
(3)调用connect()
函数向服务器发送连接请求并建立连接;
(4)调用read/recv、write/send
与客户端进行通信;
(5)调用close()
关闭套接字。
(1)调用socket()
函数打开套接字,得到套接字描述符;
(2)调用bind()
函数将套接字与IP
地址、端口号进行绑定;
(3)调用listen()
函数让服务器进程进入监听状态;
(4)调用accept()
函数获取客户端的连接请求并建立连接,若无客户端请求连接,这里会进行阻塞等待;
(5)调用read/recv、write/send
与客户端进行通信;
(6)调用close()
关闭套接字。
2. TCP
编程流程图 TCP编程过程的基本流程图如下:
二、TCP
连接建立 前边我们已经学习了socket
相关API
函数,下边我们就使用socket
创建一个TCP
协议的连接。
1. server
服务器端 首先我们来编写服务器端实现连接的程序。
1.1 需要注意的地方 (1)前边已经提过了,服务器端要用 bind()
函数将套接字与特定的 IP
地址和端口绑定起来,只有这样,流经该 IP
地址和端口的数据才能交给套接字处理。所以我们绑定的IP
一定要是运行服务器端程序的计算机本地网卡的IP
地址,注意这个IP
可能不止一个,如我在虚拟机中安装的Ubuntu
:
这就意味着我们使用bind
绑定的时候,这两个IP
都可以使用。
(2)当我们绑定的IP
不是上边的本地IP
的时候可能会报以下错误:
1 Cannot assign requested address
(3)我们要是想监听这两个IP
地址的话,就需要两个套接字,其实前边已经提过了,我们可以通过一个套接字来管理两个IP
,这个就是通过绑定INADDR_ANY
,这就带代表了我们可以监听所有本地的IP
,这样不管客户端是连接了哪个网卡,我们都可以在服务器端监听到连接状态,这样客户端便都可以正常连接过来了。不过这个直接就是一个二进制类型的IP
,我们使用的时候可以直接进行字节序转换,然后可以直接填充到相应的结构体中。或者就是直截了当,使用0.0.0.0
,这其实与INADDR_ANY
效果是一样的,个人觉得吧,这样似乎还更通用一点,可以随时改服务器绑定的IP
。
1.2 服务器端实例 点击查看实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <strings.h> #include <arpa/inet.h> #include <netinet/in.h> #include <unistd.h> void usage (char *str) ; int main (int argc, char *argv[]) { int port = -1 ; if (argc != 3 ) { usage(argv[0 ]); exit (-1 ); } port = atoi(argv[2 ]); if (port < 5000 ) { usage(argv[0 ]); exit (-1 ); } int socket_fd = -1 ; if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0 )) < 0 ) { perror ("socket" ); exit (-1 ); } struct sockaddr_in sin ; bzero (&sin , sizeof (sin )); sin .sin_family = AF_INET; sin .sin_port = htons(port); if (inet_pton(AF_INET, argv[1 ], (void *)&sin .sin_addr) != 1 ) { perror ("inet_pton" ); exit (-1 ); } if (bind(socket_fd, (struct sockaddr *)&sin , sizeof (sin )) < 0 ) { perror("bind" ); exit (-1 ); } if (listen(socket_fd, 5 ) < 0 ) { perror("listen" ); exit (-1 ); } printf ("Server starting....OK!\n" ); int newfd = -1 ; struct sockaddr_in cin ; socklen_t addrlen = sizeof (cin ); if ((newfd = accept(socket_fd, (struct sockaddr *)&cin , &addrlen)) < 0 ) { perror("accept" ); exit (-1 ); } char ipv4_addr[16 ]; if (!inet_ntop(AF_INET, (void *)&cin .sin_addr, ipv4_addr, sizeof (cin ))) { perror ("inet_ntop" ); exit (-1 ); } printf ("Clinet(%s:%d) is connected successfully!\n" , ipv4_addr, ntohs(cin .sin_port)); while (1 ) { sleep(1 ); } close(newfd); close(socket_fd); return 0 ; } void usage (char *str) { printf ("\n%s serv_ip serv_port" , str); printf ("\n\t serv_ip: server ip address" ); printf ("\n\t serv_port: server port(>5000)\n\n" ); printf ("\n\t Attention: The IP address must be the IP address of the local nic or 0.0.0.0 \n\n" ); }
2. client
客户端 接下来就是我们来编写客户端实现连接的程序啦。
2.1 需要注意的地方 (1)我们客户端connect
的时候,填写的IP
必须是运行服务器进程的计算机的本地IP
,例如,我在虚拟机的Ubuntu
中有两个网卡,这两个网卡的IP
为:
1 2 3 4 ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 192.168.10.101 netmask 255.255.255.0 broadcast 192.168.10.255 ens37: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 192.168.239.128 netmask 255.255.255.0 broadcast 192.168.239.255
这就意味着我们连接的应该是192.168.10.101
或者192.168.239.128
,若为其他,则可能会报:
1 connect: No route to host
(2)还有一种情况,就是我们服务器进程的bind()
函数绑定了一个Ip
,但是我们的客户端绑定了另一个IP
,例如,我们的服务器进程绑定的是192.168.10.101
,但是客户端通过connect
连接的却是192.168.239.128
,这时候虽然不会报上边(1)
的错误吗,但是会报如下错误:
1 connect: Connection refused
(3)若想客户端连接哪个IP
都可以,这个时候我们可以让服务器的bind()
绑定IP
的时候绑定到0.0.0.0
,或者使用INADDR_ANY
。
2.2 客户端实例 点击查看实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> void usage (char *str) ; int main (int argc, char *argv[]) { int port = -1 ; if (argc != 3 ) { usage(argv[0 ]); exit (-1 ); } port = atoi(argv[2 ]); if (port < 5000 ) { usage(argv[0 ]); exit (-1 ); } int socket_fd = -1 ; if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0 )) < 0 ) { perror ("socket" ); exit (-1 ); } struct sockaddr_in sin ; bzero (&sin , sizeof (sin )); sin .sin_family = AF_INET; sin .sin_port = htons(port); if (inet_pton(AF_INET, argv[1 ], (void *)&sin .sin_addr) != 1 ) { perror ("inet_pton" ); exit (-1 ); } if (connect(socket_fd, (struct sockaddr *)&sin , sizeof (sin )) < 0 ) { perror("connect" ); exit (-1 ); } printf ("Client staring...OK!\n" ); while (1 ) { sleep(1 ); } close(socket_fd); return 0 ; } void usage (char *str) { printf ("\n%s serv_ip serv_port" , str); printf ("\n\t serv_ip: server ip address" ); printf ("\n\t serv_port: server port(>5000)\n\n" ); }
3. Makefile
文件 由于需要生成两个可执行程序,自己输命令有些繁琐,这里使用make
来进行。
点击查看 Makefile 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 CC = gcc DEBUG = -g -O2 -Wall CFLAGS += $(DEBUG) TARGET_LIST = ${patsubst %.c, %, ${wildcard *.c}} all : $(TARGET_LIST) %.o : %.c $(CC) $(CFLAGS) -c $< -o $@ .PHONY : all clean clean_o clean_outclean : clean_o clean_out @rm -vf $(TARGET_LIST) clean_o : @rm -vf *.o clean_out : @rm -vf *.out
4. 测试结果 我们首先运行make
命令来编译生成文件:
开始测试之前吗,我们需要知道,我所使用的Ubuntu
网卡的所有IP
,我们可以使用以下命令查看:
然后我们便会看到以下信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 192.168.10.101 netmask 255.255.255.0 broadcast 192.168.10.255 inet6 fe80::8c02:d542:197d:a89b prefixlen 64 scopeid 0x20<link> ether 00:0c:29:45:70:eb txqueuelen 1000 (以太网) RX packets 0 bytes 0 (0.0 B) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 91 bytes 9743 (9.7 KB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 ens37: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 192.168.239.128 netmask 255.255.255.0 broadcast 192.168.239.255 inet6 fe80::ec67:9bd4:65eb:469f prefixlen 64 scopeid 0x20<link> ether 00:0c:29:45:70:f5 txqueuelen 1000 (以太网) RX packets 7725 bytes 2146206 (2.1 MB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 1198 bytes 101872 (101.8 KB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536 inet 127.0.0.1 netmask 255.0.0.0 inet6 ::1 prefixlen 128 scopeid 0x10<host> loop txqueuelen 1000 (本地环回) RX packets 395 bytes 30869 (30.8 KB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 395 bytes 30869 (30.8 KB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
4.1 测试1
首先我们进行服务器端绑定IP
的情况的测试,我们在终端中输入以下命令:
1 2 3 4 5 6 ./server 192.168.10.101 5001 ./server 192.168.239.128 5001 ./server 127.0.0.1 5001 ./server 0.0.0.0 5001 ./server 192.168.10.102 5001
然后会得到下图的情况:
4.2 测试2
这个测试是使用客户端连接服务器。
4.2.1 服务器IP
固定为网卡IP
1 ./server 192.168.10.101 5001
然后,会有如下提示:
之后该服务器开始阻塞等待客户端的连接。然后我们重新开一个终端,输入以下命令:
1 2 3 4 ./client 192.168.10.102 5001 ./client 192.168.239.128 5001 ./client 192.168.10.101 5001
然后我们会看到如下图的情况:
前两次的时候分别报了上边曾经说过的错误,只有最后一次是连接成功的。
4.2.2 服务器IP
固定为INADDR_ANY
为了方便测试,上边的源程序中使用的是0.0.0.0
,但是这跟INADDR_ANY
是一样的。
然后,会有如下提示:
之后该服务器开始阻塞等待客户端的连接。然后我们重新开一个终端,输入以下命令:
1 2 3 4 ./client 192.168.10.102 5001 ./client 192.168.239.128 5001 ./client 192.168.10.101 5001
然后我们会看到如下图的情况:
由于我没有设置服务器端循环等待客户端的连接,所以服务器端只能连接一个客户端,所以上图中服务器端运行了两次,看输出结果我们会发现,当服务器IP
设置为0.0.0.0
的时候,客户端连接的只要是运行服务器端的计算机的本地网卡IP
,都可以正常连接到服务器。
5. 端口号问题 从上边的两个测试中,有木有发现一个问题,就是每一次客户端连接到服务器的时候,明明都是同一个程序,但是端口号却每次都不一样,而且这个端口号还跟我们客户端连接的时候的不一样。
后来查阅了一些资料,了解到服务器用listen()
监听时要指定一个监听端口号,例如上边的5001
,而客户端用connect()
连接时要指定服务器地址以及服务器的提供的连接端口号(也就是5001
),客户端本机向外连接的端口号由本机随机产生 ,这样的好处就是可以避免端口冲突。
还记得上边的TCP
编程流程图吧,里边有一个虚框框住的bind()
,这时候一想,是不是发现前边我们的服务器好像用bind()
函数绑定了IP
和端口号,但是客户端的程序却没有这个操作,根据bind()
函数的功能,我们很容易想到,如果说客户端也进行Ip
和端口号的绑定,那这个服务器读取到的端口号是否就是固定的呢?答案是肯定的,不过需要注意的是,绑定的端口不能与其他的端口冲突。下边可以进行一个测试。
我们修改客户端程序如下,这里需要注意要保证客户端绑定的端口号要跟连接到服务器的端口号不同:
点击查看客户端修改后的完整程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> void usage (char *str) ; int main (int argc, char *argv[]) { int port = -1 ; int portClient = -1 ; if (argc != 4 ) { usage(argv[0 ]); exit (-1 ); } port = atoi(argv[2 ]); portClient = atoi(argv[3 ]); if (port < 5000 || portClient < 5000 || (port == portClient)) { usage(argv[0 ]); exit (-1 ); } int socket_fd = -1 ; if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0 )) < 0 ) { perror ("socket" ); exit (-1 ); } struct sockaddr_in sin ; bzero (&sin , sizeof (sin )); sin .sin_family = AF_INET; sin .sin_port = htons(port); if (inet_pton(AF_INET, argv[1 ], (void *)&sin .sin_addr) != 1 ) { perror ("inet_pton" ); exit (-1 ); } struct sockaddr_in sinClient ; bzero (&sinClient, sizeof (sinClient)); sinClient.sin_family = AF_INET; sinClient.sin_port = htons(portClient); if (inet_pton(AF_INET, argv[1 ], (void *)&sinClient.sin_addr) != 1 ) { perror ("inet_pton" ); exit (-1 ); } if (bind(socket_fd, (struct sockaddr *)&sinClient, sizeof (sinClient)) < 0 ) { perror("bind" ); exit (-1 ); } if (connect(socket_fd, (struct sockaddr *)&sin , sizeof (sin )) < 0 ) { perror("connect" ); exit (-1 ); } printf ("Client staring...OK!\n" ); while (1 ) { sleep(1 ); } close(socket_fd); return 0 ; } void usage (char *str) { printf ("\n%s serv_ip serv_port" , str); printf ("\n\t serv_ip: server ip address" ); printf ("\n\t serv_port: server port(>5000)\n\n" ); printf ("\n\t client_port: client portClient(>5000 && !=port )\n\n" ); }
这个时候我们按照相同的方式分别运行两个程序:
1 2 3 ./server 0.0.0.0 5001 ./client 192.168.10.101 5001 5002
会发现如图中,服务器端读取到的端口号与我们绑定的端口号一样了。
6. 有一个问题 不知道前边有没有发现一个问题,就是有的时候我们服务器和客户端退出后,立刻重新运行服务器端,可能会发现如下报错:
1 bind: Address already in use
这是因为,我们给给某一进程分配端口,但是进程结束没有释放这一端口 ,我们可以在创建完socket
套接字后添加以下内容:
1 2 3 int b_reuse = 1 ;setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof (int ));
函数介绍以及各个参数说明会在后边学习网络属性设置的时候会再说。
三、数据传输 当我们建立好连接后,接下来就是数据的传输了。
1. 单向传输 1.1 server
服务器端 点击查看实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <strings.h> #include <arpa/inet.h> #include <netinet/in.h> #include <unistd.h> #include <string.h> void usage (char *str) ; int main (int argc, char *argv[]) { int port = -1 ; if (argc != 3 ) { usage(argv[0 ]); exit (-1 ); } port = atoi(argv[2 ]); if (port < 5000 ) { usage(argv[0 ]); exit (-1 ); } int socket_fd = -1 ; if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0 )) < 0 ) { perror ("socket" ); exit (-1 ); } int b_reuse = 1 ; setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof (int )); struct sockaddr_in sin ; bzero (&sin , sizeof (sin )); sin .sin_family = AF_INET; sin .sin_port = htons(port); if (inet_pton(AF_INET, argv[1 ], (void *)&sin .sin_addr) != 1 ) { perror ("inet_pton" ); exit (-1 ); } if (bind(socket_fd, (struct sockaddr *)&sin , sizeof (sin )) < 0 ) { perror("bind" ); exit (-1 ); } if (listen(socket_fd, 5 ) < 0 ) { perror("listen" ); exit (-1 ); } printf ("Server starting....OK!\n" ); int newfd = -1 ; struct sockaddr_in cin ; socklen_t addrlen = sizeof (cin ); if ((newfd = accept(socket_fd, (struct sockaddr *)&cin , &addrlen)) < 0 ) { perror("accept" ); exit (-1 ); } char ipv4_addr[16 ]; if (!inet_ntop(AF_INET, (void *)&cin .sin_addr, ipv4_addr, sizeof (cin ))) { perror ("inet_ntop" ); exit (-1 ); } printf ("Clinet(%s:%d) is connected successfully![newfd=%d]\n" , ipv4_addr, ntohs(cin .sin_port), newfd); int ret = -1 ; char buf[BUFSIZ]; while (1 ) { bzero(buf, BUFSIZ); do { ret = read(newfd, buf, BUFSIZ - 1 ); }while (ret < 0 && EINTR == errno); if (ret < 0 ) { perror("read" ); exit (-1 ); } if (!ret) break ; printf ("Receive data: %s\n" , buf); if (!strncasecmp (buf, "quit" , strlen ("quit" ))) { printf ("Client is exiting!\n" ); break ; } } close(newfd); close(socket_fd); return 0 ; } void usage (char *str) { printf ("\n%s serv_ip serv_port" , str); printf ("\n\t serv_ip: server ip address" ); printf ("\n\t serv_port: server port(>5000)\n\n" ); printf ("\n\t Attention: The IP address must be the IP address of the local nic or 0.0.0.0 \n\n" ); }
1.2 client
客户端 点击查看实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> void usage (char *str) ; int main (int argc, char *argv[]) { int port = -1 ; int portClient = -1 ; if (argc != 4 ) { usage(argv[0 ]); exit (-1 ); } port = atoi(argv[2 ]); portClient = atoi(argv[3 ]); if (port < 5000 || portClient < 5000 || (port == portClient)) { usage(argv[0 ]); exit (-1 ); } int socket_fd = -1 ; if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0 )) < 0 ) { perror ("socket" ); exit (-1 ); } int b_reuse = 1 ; setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof (int )); struct sockaddr_in sin ; bzero (&sin , sizeof (sin )); sin .sin_family = AF_INET; sin .sin_port = htons(port); if (inet_pton(AF_INET, argv[1 ], (void *)&sin .sin_addr) != 1 ) { perror ("inet_pton" ); exit (-1 ); } struct sockaddr_in sinClient ; bzero(&sinClient, sizeof (sinClient)); sinClient.sin_family = AF_INET; sinClient.sin_port = htons(portClient); if (inet_pton(AF_INET, argv[1 ], (void *)&sinClient.sin_addr) != 1 ) { perror ("inet_pton" ); exit (-1 ); } if (bind(socket_fd, (struct sockaddr *)&sinClient, sizeof (sinClient)) < 0 ) { perror("bind" ); exit (-1 ); } if (connect(socket_fd, (struct sockaddr *)&sin , sizeof (sin )) < 0 ) { perror("connect" ); exit (-1 ); } printf ("Client staring...OK!\n" ); int ret = -1 ; char buf[BUFSIZ]; while (1 ) { bzero (buf, BUFSIZ); if (fgets(buf, BUFSIZ - 1 , stdin ) == NULL ) { continue ; } do { ret = write(socket_fd, buf, strlen (buf)); }while (ret < 0 && EINTR == errno); if (!strncasecmp (buf, "quit" , strlen ("quit" ))) { printf ("Client is exiting!\n" ); break ; } } close(socket_fd); return 0 ; } void usage (char *str) { printf ("\n%s serv_ip serv_port" , str); printf ("\n\t serv_ip: server ip address" ); printf ("\n\t serv_port: server port(>5000)\n\n" ); printf ("\n\t client_port: client portClient(>5000 && !=port )\n\n" ); }
1.3 Makefile
由于需要生成两个可执行程序,自己输命令有些繁琐,这里使用make
来进行。
点击查看 Makefile 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 CC = gcc DEBUG = -g -O2 -Wall CFLAGS += $(DEBUG) TARGET_LIST = ${patsubst %.c, %, ${wildcard *.c}} all : $(TARGET_LIST) %.o : %.c $(CC) $(CFLAGS) -c $< -o $@ .PHONY : all clean clean_o clean_outclean : clean_o clean_out @rm -vf $(TARGET_LIST) clean_o : @rm -vf *.o clean_out : @rm -vf *.out
1.4 测试结果 我们执行以下命令编译链接程序,生成两个可执行文件:
然后会有如下提示,并生成两个可执行文件吗,分别为服务器端程序server
和客户端程序client
:
1 2 gcc -g -O2 -Wall client.c -o client gcc -g -O2 -Wall server.c -o server
对于服务器端,我们执行以下命令启动服务器进程:
1 ./server 0.0.0.0 5001 # 允许监听所有网卡IP及端口
对于客户端,我们执行以下命令启动客户端进程:
1 ./client 192.168.10.101 5001 5003 # 连接到本地一块网卡的IP,并设置客户端向外发送数据的端口为5003
然后我们就会看到如下现象:
可以发现我们发送的数据,在客户端都被打印了出来,说明客户端与服务器端可以进行正常通信。
2. 双向传输 在TCP
中,有两组数据的接收和发送,这里客户端向服务器端发送数据的时候我们使用read()/write()
,而服务器端向客户端回复的时候我们使用recv()/send()
函数,这样也熟悉一下这两组数据接收发送函数。
2.1 server
服务器端 点击查看实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <strings.h> #include <arpa/inet.h> #include <netinet/in.h> #include <unistd.h> #include <string.h> void usage (char *str) ; int main (int argc, char *argv[]) { int port = -1 ; if (argc != 3 ) { usage(argv[0 ]); exit (-1 ); } port = atoi(argv[2 ]); if (port < 5000 ) { usage(argv[0 ]); exit (-1 ); } int socket_fd = -1 ; if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0 )) < 0 ) { perror ("socket" ); exit (-1 ); } int b_reuse = 1 ; setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof (int )); struct sockaddr_in sin ; bzero (&sin , sizeof (sin )); sin .sin_family = AF_INET; sin .sin_port = htons(port); if (inet_pton(AF_INET, argv[1 ], (void *)&sin .sin_addr) != 1 ) { perror ("inet_pton" ); exit (-1 ); } if (bind(socket_fd, (struct sockaddr *)&sin , sizeof (sin )) < 0 ) { perror("bind" ); exit (-1 ); } if (listen(socket_fd, 5 ) < 0 ) { perror("listen" ); exit (-1 ); } printf ("Server starting....OK!\n" ); int newfd = -1 ; struct sockaddr_in cin ; socklen_t addrlen = sizeof (cin ); if ((newfd = accept(socket_fd, (struct sockaddr *)&cin , &addrlen)) < 0 ) { perror("accept" ); exit (-1 ); } char ipv4_addr[16 ]; if (!inet_ntop(AF_INET, (void *)&cin .sin_addr, ipv4_addr, sizeof (cin ))) { perror ("inet_ntop" ); exit (-1 ); } printf ("Clinet(%s:%d) is connected successfully![newfd=%d]\n" , ipv4_addr, ntohs(cin .sin_port), newfd); int ret = -1 ; char buf[BUFSIZ]; char replay[BUFSIZ]; while (1 ) { bzero(buf, BUFSIZ); bzero(replay, BUFSIZ); do { ret = read(newfd, buf, BUFSIZ - 1 ); }while (ret < 0 && EINTR == errno); if (ret < 0 ) { perror("read" ); exit (-1 ); } if (!ret) break ; printf ("Receive data: %s\n" , buf); strcat (replay, buf); ret = send(newfd, replay, strlen (replay), 0 ); if (ret < 0 ) { perror("send" ); exit (-1 ); } if (!strncasecmp(buf, "quit" , strlen ("quit" ))) { printf ("Client is exiting!\n" ); break ; } } close(newfd); close(socket_fd); return 0 ; } void usage (char *str) { printf ("\n%s serv_ip serv_port" , str); printf ("\n\t serv_ip: server ip address" ); printf ("\n\t serv_port: server port(>5000)\n\n" ); printf ("\n\t Attention: The IP address must be the IP address of the local nic or 0.0.0.0 \n\n" ); }
2.2 client
客户端 点击查看实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> void usage (char *str) ; int main (int argc, char *argv[]) { int port = -1 ; int portClient = -1 ; if (argc != 4 ) { usage(argv[0 ]); exit (-1 ); } port = atoi(argv[2 ]); portClient = atoi(argv[3 ]); if (port < 5000 || portClient < 5000 || (port == portClient)) { usage(argv[0 ]); exit (-1 ); } int socket_fd = -1 ; if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0 )) < 0 ) { perror ("socket" ); exit (-1 ); } int b_reuse = 1 ; setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof (int )); struct sockaddr_in sin ; bzero (&sin , sizeof (sin )); sin .sin_family = AF_INET; sin .sin_port = htons(port); if (inet_pton(AF_INET, argv[1 ], (void *)&sin .sin_addr) != 1 ) { perror ("inet_pton" ); exit (-1 ); } struct sockaddr_in sinClient ; bzero(&sinClient, sizeof (sinClient)); sinClient.sin_family = AF_INET; sinClient.sin_port = htons(portClient); if (inet_pton(AF_INET, argv[1 ], (void *)&sinClient.sin_addr) != 1 ) { perror ("inet_pton" ); exit (-1 ); } if (bind(socket_fd, (struct sockaddr *)&sinClient, sizeof (sinClient)) < 0 ) { perror("bind" ); exit (-1 ); } if (connect(socket_fd, (struct sockaddr *)&sin , sizeof (sin )) < 0 ) { perror("connect" ); exit (-1 ); } printf ("Client staring...OK!\n" ); int ret = -1 ; char buf[BUFSIZ]; char replay[BUFSIZ]; while (1 ) { bzero (buf, BUFSIZ); bzero (replay, BUFSIZ); printf (">" ); if (fgets(buf, BUFSIZ - 1 , stdin ) == NULL ) { continue ; } do { ret = write(socket_fd, buf, strlen (buf)); }while (ret < 0 && EINTR == errno); ret = recv(socket_fd, replay, BUFSIZ, 0 ); if (ret < 0 ) { perror("recv" ); exit (-1 ); } printf ("server replay:%s\n" , replay); if (!strncasecmp(buf, "quit" , strlen ("quit" ))) { printf ("Client is exiting!\n" ); break ; } } close(socket_fd); return 0 ; } void usage (char *str) { printf ("\n%s serv_ip serv_port" , str); printf ("\n\t serv_ip: server ip address" ); printf ("\n\t serv_port: server port(>5000)\n\n" ); printf ("\n\t client_port: client portClient(>5000 && !=port )\n\n" ); }
2.3 Makefile
由于需要生成两个可执行程序,自己输命令有些繁琐,这里使用make
来进行。
点击查看 Makefile 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 CC = gcc DEBUG = -g -O2 -Wall CFLAGS += $(DEBUG) TARGET_LIST = ${patsubst %.c, %, ${wildcard *.c}} all : $(TARGET_LIST) %.o : %.c $(CC) $(CFLAGS) -c $< -o $@ .PHONY : all clean clean_o clean_outclean : clean_o clean_out @rm -vf $(TARGET_LIST) clean_o : @rm -vf *.o clean_out : @rm -vf *.out
2.4 测试结果 我们执行以下命令编译链接程序,生成两个可执行文件:
然后会有如下提示,并生成两个可执行文件吗,分别为服务器端程序server
和客户端程序client
:
1 2 gcc -g -O2 -Wall client.c -o client gcc -g -O2 -Wall server.c -o server
对于服务器端,我们执行以下命令启动服务器进程:
1 ./server 0.0.0.0 5001 # 允许监听所有网卡IP及端口
对于客户端,我们执行以下命令启动客户端进程:
1 ./client 192.168.10.101 5001 5003 # 连接到本地一块网卡的IP,并设置客户端向外发送数据的端口为5003
然后我们就会看到如下现象:
可以发现我们发送的数据,在服务器端都被打印了出来,客户端也打印出了服务器端发送的数据。说明客户端与服务器端可以进行正常双向通信。