当前位置:   article > 正文

【网络通信与信息安全】之深入解析TCP与UDP传输协议_tcp负责心跳 udp负责指令传输

tcp负责心跳 udp负责指令传输

一、什么是 socket

  • Socket 的英文原义是“孔”或“插座”。在编程中,Socket 被称做套接字,是网络通信中的一种约定。Socket 编程的应用无处不在,我们平时用的 QQ、微信、浏览器等程序,都与 Socket 编程有关。
  • 那么我们使用浏览器查资料,这个过程的技术原理是怎样的呢?如下所示:

在这里插入图片描述

  • 使用浏览器,有两个重要的名词:服务端与客户端,Socket 编程的目的就是实现这两端之间的通信。

二、Socket 编程的重要概念

① IP 地址

  • IP 地址(Internet Protocol Address)是指互联网协议地址,又译为网际协议地址。IP 地址被用来给 Internet 上的电脑一个编号,可以把“个人电脑”比作“一台电话”,那么“IP 地址”就相当于“电话号码”。若计算机 1 知道计算机 2 的 IP 地址,则计算机 1 就能访问计算机 2。
  • IP 地址是一个 32 位的二进制数,通常被分割为 4 个“8位二进制数”(也就是 4 个字节)。IP 地址通常用点分十进制表示成(a.b.c.d)的形式,其中,a,b,c,d 都是 0~255 之间的十进制整数。例:点分十进 IP 地址(100.4.5.6),实际上是 32 位二进制数(01100100.00000100.00000101.00000110)。
  • IP 地址有 IPv4 与 IPv6 之分,现在用得较多的是 IPv4。其中,有一个特殊的 IP 地址需要记住:127.0.0.1,这是回送地址,即本地机,一般用来测试使用。

② TCP/IP 端口

  • 若计算机 1 知道计算机 2 的 IP 地址,则计算机 1 就能访问计算机 2。但是,要访问计算机 2 中的不同的应用软件,则还得需要一个信息:端口。
  • 服务端口,最多可以有65536个,使用 16bit 进行编号,即其范围为:0 ~ 65535。但 0 ~ 1023 的端口一般由系统分配给特定的服务程序,例如 Web 服务的端口号为 80,FTP 服务的端口号为 21 等。

③ 协议

  • 协议(Protocol)是通信双方进行数据交互的一种约定,如 TCP、UDP 协议。
  • TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议,数据可以准确发送,数据丢失会重发,TCP 协议常用于 web 应用中。
  • TCP 连接(三次握手):TCP 传输起始时,客户端、服务端要完成三次数据交互工作才能建立连接,常称为三次握手。可形象比喻为如下对话:
    • 客户端:服务端您好,我有数据要发给你,请求您开通访问权限。
    • 服务端:客户端您好,已给您开通权限,您可以发送数据了。
    • 客户端:收到,谢谢。
  • TCP 连接(三次握手)具体示意图为:

在这里插入图片描述

  • 说明:SYN 和 ACK 是都是标志位,其中 SYN 代表新建一个连接,ACK 代表确认。其中 m、n 都是随机数。具体说明如:
    • 第一次握手:SYN 标志位被置位,客户端向服务端发送一个随机数 m。
    • 第二次握手:ACK、SYN 标志位被置位。服务端向客户端发送 m+1 表示确认刚才收到的数据,同时向客户端发送一个随机数 n。
    • 第三次握手:ACK 标志被置位,客户端向服务端发送 n+1 表示确认收到数据。
  • TCP 断开(四次挥手):TCP 断开连接时,客户端、服务端要完成四次数据交互工作才能建立连接,常称为四次挥手。可形象比喻为如下对话:
    • 客户端:服务端您好,我发送数据完毕了,即将和您断开连接。
    • 服务端:客户端您好,我稍稍准备一下,再给您断开
    • 服务端:客户端您好,我准备好了,您可以断开连接了。
    • 客户端:好的,合作愉快。
  • TCP 断开(四次挥手)具体示意图为:

在这里插入图片描述

  • FIN 也是一个标志位,代表断开连接,类似三次握手。
  • 为什么建立连接只需要三次数据交互,而断开连接需要四次呢?
    • 建立连接时,服务端在监听状态下,收到建立连接请求的 SYN 报文后,把 ACK 和 SYN 放在一个报文里发送给客户端。
    • 而关闭连接时,当收到对方的 FIN 报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方也未必全部数据都发送给对方了,所以己方可以立即 close,也可以发送一些数据给对方后,再发送 FIN 报文给对方来表示同意现在关闭连接,因此,己方 ACK 和 FIN 一般都会分开发送。
  • UDP 协议:UDP(User Datagram Protocol, 用户数据报协议)是一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,可以保证通讯效率,传输延时小。例如视频聊天应用中用的就是 UDP 协议,这样可以保证及时丢失少量数据,视频的显示也不受很大影响。
  • 什么是协议族?协议族是多个协议的统称。比如 TCP/IP 协议族,其不仅仅是 TCP 协议、IP 协议,而是多个协议的集合,其包含 IP、TCP、UDP、FTP、SMTP 等协议。

三、socket 编程的 API 接口

① Linux 下的 socket API 接口

  • 创建 socket:socket()函数
    • 函数原型,如下所示:
	int socket(int af, int type, int protocol);
  • 1
    • 函数说明:
      • af 参数:af 为地址族(Address Family),也就是 IP 地址类型,常用的有 AF_INET 和 AF_INET6,其前缀也可以是 PF(Protocol Family),即 PF_INET 和 PF_INET6。
      • type 参数:type 为数据传输方式,常用的有 面向连接(SOCK_STREAM)方式(即 TCP) 和 无连接(SOCK_DGRAM)的方式(即 UDP)。
      • protocol 参数:protocol 表示传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议。
    • 创建 TCP 套接字:
	int tcp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  • 1
    • 创建 UDP 套接字:
	int udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
  • 1
  • 绑定套接字:bind() 函数
    • 函数原型,如下所示:
	int bind(int sock, struct sockaddr *addr, socklen_t addrlen); 
  • 1
    • 函数说明:
      • sock 参数:sock 为 socket 文件描述符。
      • addr 参数:addr 为 sockaddr 结构体变量的指针。
      • addrlen 参数:addrlen 为 addr 变量的大小,可由 sizeof() 计算得出。
    • 将创建的套接字 ServerSock 与本地 IP127.0.0.1、端口 1314 进行绑定:
	/* 创建服务端socket */
	int ServerSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);/* 设置服务端信息 */
	struct sockaddr_in ServerSockAddr;
	memset(&ServerSockAddr, 0, sizeof(ServerSockAddr));     
	// 给结构体ServerSockAddr清零
	ServerSockAddr.sin_family = PF_INET;                    		
	// 使用IPv4地址
	ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
	// 本机IP地址
	ServerSockAddr.sin_port = htons(1314); // 端口/* 绑定套接字 */
	bind(ServerSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR));
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
    • 其中 struct sockaddr_in 类型的结构体变量用于保存 IPv4 的 IP 信息,若是 IPv6,则有对应的结构体:
	struct sockaddr_in6 {     
		sa_family_t sin6_family;    // 地址类型,取值为AF_INET6    
		in_port_t sin6_port;        // 16位端口号    
		uint32_t sin6_flowinfo;     // IPv6流信息    
		struct in6_addr sin6_addr;  // 具体的IPv6地址    
		uint32_t sin6_scope_id;     // 接口范围ID
	};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 建立连接:connect() 函数
    • 函数原型,参数与 bind() 的参数类似,如下所示:
	int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen);  
  • 1
    • 使用示例:
	int ClientSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	connect(ClientSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR));
  • 1
  • 2
  • 监听:listen() 函数
    • 函数如下所示:
	int listen(int sock, int backlog);
  • 1
    • 函数的参数说明:
      • sock 参数:sock 为需要进入监听状态的套接字。
      • backlog 参数:backlog 为请求队列的最大长度。
    • 使用示例:
	/* 进入监听状态 */
	listen(ServerSock, 10);
  • 1
  • 2
  • 接收请求:accept() 函数
    • 函数如下所示:
	int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);
  • 1
    • 函数参数说明:
      • sock 参数:sock 为服务器端套接字。
      • addr参数:addr 为 sockaddr_in 结构体变量。
      • addrlen 参数:addrlen 为参数 addr 的长度,可由 sizeof() 求得。
      • 返回值:一个新的套接字,用于与客户端通信。
    • 使用示例:
	/* 监听客户端请求,accept函数返回一个新的套接字,发送和接收都是用这个套接字 */
	int ClientSock = accept(ServerSock, (SOCKADDR*)&ClientAddr, &len);
  • 1
  • 2
  • 关闭:close() 函数
    • 函数如下:
	int close(int fd);
  • 1
    • 函数参数 fd:要关闭的文件描述符。
    • 使用示例:
	close(ServerSock);
  • 1
  • 数据的接收和发送
    • 数据收发函数有几组:
	read()/write()
	recv()/send()
	readv()/writev()
	recvmsg()/sendmsg()
	recvfrom()/sendto()
  • 1
  • 2
  • 3
  • 4
  • 5
    • 函数原型如下:
	ssize_t read(int fd, void *buf, size_t count);
	ssize_t write(int fd, const void *buf, size_t count);
	ssize_t send(int sockfd, const void *buf, size_t len, int flags);
	ssize_t recv(int sockfd, void *buf, size_t len, int flags);
	ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
	                      const struct sockaddr *dest_addr, socklen_t addrlen);
	ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
	                        struct sockaddr *src_addr, socklen_t *addrlen);
	ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
	ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
    • recv() 函数:
      • sockfd 参数:sockfd 为要接收数据的套接字。
      • buf 参数:buf 为要接收的数据的缓冲区地址。
      • len 参数:len 为要接收的数据的字节数。
      • flags 参数:flags 为接收数据时的选项,常设为 0。
	ssize_t recv(int sockfd, void *buf, size_t len, int flags);
  • 1
    • send() 函数:
      • sockfd 参数:sockfd 为要发送数据的套接字。
      • buf 参数:buf 为要发送的数据的缓冲区地址。
      • len 参数:len 为要发送的数据的字节数。
      • flags 参数:flags 为发送数据时的选项,常设为 0。
	ssize_t send(int sockfd, const void *buf, size_t len, int flags);
  • 1
    • recvfrom() 函数:
      • sock:用于接收 UDP 数据的套接字;
      • buf:保存接收数据的缓冲区地址;
      • nbytes:可接收的最大字节数(不能超过 buf 缓冲区的大小);
      • flags:可选项参数,若没有可传递 0;
      • from:存有发送端地址信息的 sockaddr 结构体变量的地址;
      • addrlen:保存参数 from 的结构体变量长度的变量地址值。
	ssize_t recvfrom(int sock, void *buf, size_t nbytes, int flags, struct sockadr *from, socklen_t *addrlen);
  • 1
    • sendto() 函数:
      • sock:用于传输 UDP 数据的套接字;
      • buf:保存待传输数据的缓冲区地址;
      • nbytes:带传输数据的长度(以字节计);
      • flags:可选项参数,若没有可传递 0;
      • to:存有目标地址信息的 sockaddr 结构体变量的地址;
      • addrlen:传递给参数 to 的地址值结构体变量的长度。
	ssize_t sendto(int sock, void *buf, size_t nbytes, int flags, struct sockaddr *to, socklen_t addrlen);
  • 1

② windows 下的 socket API 接口

	SOCKET socket(int af, int type, int protocol);
	int bind(SOCKET sock, const struct sockaddr *addr, int addrlen);
	int connect(SOCKET sock, const struct sockaddr *serv_addr, int addrlen);
	int listen(SOCKET sock, int backlog);
	SOCKET accept(SOCKET sock, struct sockaddr *addr, int *addrlen); 
	int closesocket( SOCKET s);
	int send(SOCKET sock, const char *buf, int len, int flags);
	int recv(SOCKET sock, char *buf, int len, int flags);
	int recvfrom(SOCKET sock, char *buf, int nbytes, int flags, const struct sockaddr *from, int *addrlen);
	int sendto(SOCKET sock, const char *buf, int nbytes, int flags, const struct sockadr *to, int addrlen);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

③ TCP、UDP 通信的 socket 编程流程图

  • TCP 通信 socket 编程流程:

在这里插入图片描述

  • UDP 通信 socket 编程流程:

在这里插入图片描述

四、socket 的应用实例

① 基于 TCP 的本地客户端、服务端信息交互实例

  • 本例的例子实现的功能为:本地 TCP 客户端往本地 TCP 服务端发送数据,TCP 服务端收到数据则会打印输出,同时把原数据返回给 TCP 客户端。这个例子类似于在做单片机的串口实验时,串口上位机往我们的单片机发送数据,单片机收到数据则把该数据原样返回给上位机。
  • windows 的程序:
    • 服务端程序 tcp_server.c:
	#include <stdio.h>
	#include <winsock2.h>
	
	#define BUF_LEN  100
	
	int main(void)
	{
		WSADATA wd;
		SOCKET ServerSock, ClientSock;
		char Buf[BUF_LEN] = {0};
		SOCKADDR ClientAddr;
		SOCKADDR_IN ServerSockAddr;
		int addr_size = 0, recv_len = 0;
		
		/* 初始化操作sock需要的DLL */
		WSAStartup(MAKEWORD(2,2),&wd);  
		
		/* 创建服务端socket */
		if (-1 == (ServerSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)))
		{
			printf("socket error!\n");
			exit(1);
		}
		
		/* 设置服务端信息 */
	    memset(&ServerSockAddr, 0, sizeof(ServerSockAddr)); 	// 给结构体ServerSockAddr清零
	    ServerSockAddr.sin_family = AF_INET;  					// 使用IPv4地址
	    ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");// 本机IP地址
	    ServerSockAddr.sin_port = htons(1314);  				// 端口
		
		/* 绑定套接字 */
	    if (-1 == bind(ServerSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR)))
		{
			printf("bind error!\n");
			exit(1);
		}
			
		/* 进入监听状态 */
		if (-1 == listen(ServerSock, 10))
		{
			printf("listen error!\n");
			exit(1);
		}
		
		addr_size = sizeof(SOCKADDR);
	
		while (1)
		{
			/* 监听客户端请求,accept函数返回一个新的套接字,发送和接收都是用这个套接字 */
			if (-1 == (ClientSock = accept(ServerSock, (SOCKADDR*)&ClientAddr, &addr_size)))
			{
				printf("socket error!\n");
				exit(1);
			}
	
			/* 接受客户端的返回数据 */
			int recv_len = recv(ClientSock, Buf, BUF_LEN, 0);
			printf("客户端发送过来的数据为:%s\n", Buf);
			
			/* 发送数据到客户端 */
			send(ClientSock, Buf, recv_len, 0);
			
			/* 关闭客户端套接字 */
			closesocket(ClientSock);
			
			/* 清空缓冲区 */
			memset(Buf, 0, BUF_LEN);  
		}
	
		/*如果有退出循环的条件,这里还需要清除对socket库的使用*/
		/* 关闭服务端套接字 */
		//closesocket(ServerSock);
	    /* WSACleanup();*/
	
		return 0;
}
  • 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
    • 客户端程序 tcp_client.c:
	#include <stdio.h>
	#include <winsock2.h>
	
	#define BUF_LEN  100
	
	int main(void) {
		WSADATA wd;
		SOCKET ClientSock;
		char Buf[BUF_LEN] = {0};
		SOCKADDR_IN  ServerSockAddr;
		
		/* 初始化操作sock需要的DLL */
		WSAStartup(MAKEWORD(2,2),&wd);  
		
		/* 向服务器发起请求 */
	    memset(&ServerSockAddr, 0, sizeof(ServerSockAddr));  
	    ServerSockAddr.sin_family = AF_INET;
	    ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
	    ServerSockAddr.sin_port = htons(1314);
		
		while (1)
		{
			/* 创建客户端socket */
			if (-1 == (ClientSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)))
			{
				printf("socket error!\n");
				exit(1);
			}
			if (-1 == connect(ClientSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR)))
			{
				printf("connect error!\n");
				exit(1);
			}
			printf("请输入一个字符串,发送给服务端:");
			gets(Buf);
			/* 发送数据到服务端 */
			send(ClientSock, Buf, strlen(Buf), 0);
			
			/* 接受服务端的返回数据 */
			recv(ClientSock, Buf, BUF_LEN, 0);
			printf("服务端发送过来的数据为:%s\n", Buf);
			
			memset(Buf, 0, BUF_LEN);   // 重置缓冲区
			closesocket(ClientSock);   // 关闭套接字
		}
		
		// WSACleanup();  /*如果有退出循环的条件,这里还需要清除对socket库的使用*/
		return 0;
	}
  • 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
  • 上面的 IP 地址概念那一部分中,有强调 127.0.0.1 这个 IP 是一个特殊的 IP 地址,这是回送地址,即本地机,一般用来测试使用。此外,端口设置为 1314,这是随意设置的,只要范围在 1024~65536 之间就可以。
  • 使用 gcc 编译器编译,编译命令如下:
	gcc tcp_client.c -o tcp_client.exe -lwsock32
	gcc tcp_server.c -o tcp_server.exe -lwsock32
  • 1
  • 2
  • 这里必须要加 -lwsock32 这个参数用于链接 windows 下 socket 编程必须的 winsock2 这个库。若是使用集成开发环境,则需要把 wsock32.lib 放在工程目录下,并在代码中 #include <winsock2.h> 下面加上一行 #pragma comment(lib, “ws2_32.lib”) 代码。
  • 先启动服务端程序 tcp_server,再启动客户端程序 tcp_client,并在客户端中输入字符串,则当服务端会接收到字符串时会打印输出,与此同时也会往客户端返回相同的数据:
	// tcp_server
	客户端发送过来的数据为:hello
	客户端发送过来的数据为:5201314

	// tcp_client
	请输入一个字符串,发送给服务端:hello
	服务端发送过来的数据为:hello
	请输入一个字符串,发送给服务端:5201314
	服务端发送过来的数据为:5201314
	请输入一个字符串,发送给服务端:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • Linux 程序
    • 在 linux 下,“一切都是文件”,所以这里的套接字也当做文件来看待。
    • 服务端程序 linux_tcp_server.c:
	#include <stdio.h>
	#include <string.h>
	#include <stdlib.h>
	#include <unistd.h>
	#include <arpa/inet.h>
	#include <sys/socket.h>
	#include <netinet/in.h>
	
	#define BUF_LEN  100
	
	int main(void) {
		int ServerFd, ClientFd;				
		char Buf[BUF_LEN] = {0};
		struct sockaddr ClientAddr;
		int addr_len = 0, recv_len = 0;
		struct sockaddr_in ServerSockAddr;  
		int optval = 1; 
		
		/* 创建服务端文件描述符 */
		if (-1 == (ServerFd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)))
		{
			printf("socket error!\n");
			exit(1);
		}
		
		/* 设置服务端信息 */
		
	    memset(&ServerSockAddr, 0, sizeof(ServerSockAddr)); 	// 给结构体ServerSockAddr清零
	    ServerSockAddr.sin_family = AF_INET;  					// 使用IPv4地址
	    ServerSockAddr.sin_addr.s_addr = htonl(INADDR_ANY);		// 自动获取IP地址
	    ServerSockAddr.sin_port = htons(6666);  				// 端口
		
	
		// 设置地址和端口号可以重复使用  
	    if (setsockopt(ServerFd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0)
		{
			printf("setsockopt error!\n");
			exit(1);
		}
		
		/* 绑定操作,绑定前加上上面的socket属性可重复使用地址 */
	    if (-1 == bind(ServerFd, (struct sockaddr*)&ServerSockAddr, sizeof(struct sockaddr)))
		{
			printf("bind error!\n");
			exit(1);
		}
			
		/* 进入监听状态 */
		if (-1 == (listen(ServerFd, 10)))
		{
			printf("listen error!\n");
			exit(1);
		}
		
		addr_len = sizeof(struct sockaddr);
	
		while (1)
		{
			/* 监听客户端请求,accept函数返回一个新的套接字,发送和接收都是用这个套接字 */
			if (-1 == (ClientFd = accept(ServerFd, (struct sockaddr*)&ClientAddr, &addr_len)))
			{
				printf("accept error!\n");
				exit(1);
			}
	
			/* 接受客户端的返回数据 */
			if ((recv_len = recv(ClientFd, Buf, BUF_LEN, 0)) < 0)
			{
				printf("recv error!\n");
				exit(1);
			}
			
			printf("客户端发送过来的数据为:%s\n", Buf);
			
			/* 发送数据到客户端 */
			send(ClientFd, Buf, recv_len, 0);
			
			/* 关闭客户端套接字 */
			close(ClientFd);
			
			/* 清空缓冲区 */
			memset(Buf, 0, BUF_LEN);  
		}
		return 0;
	}
  • 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
    • 客户端程序 linux_tcp_client.c:
	#include <stdio.h>
	#include <string.h>
	#include <stdlib.h>
	#include <unistd.h>
	#include <arpa/inet.h>
	#include <sys/socket.h>
	
	#define BUF_LEN  100
	
	int main(void) {
		int ClientFd;
		char Buf[BUF_LEN] = {0};
		struct sockaddr_in  ServerSockAddr;
		
		
		/* 向服务器发起请求 */
	    memset(&ServerSockAddr, 0, sizeof(ServerSockAddr));  
	    ServerSockAddr.sin_family = AF_INET;
	    ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
	    ServerSockAddr.sin_port = htons(6666);
		
		while (1)
		{
			/* 创建客户端socket */
			if (-1 == (ClientFd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)))
			{
				printf("socket error!\n");
				exit(1);
			}
			
			/* 连接 */
			if (-1 == connect(ClientFd, (struct sockaddr*)&ServerSockAddr, sizeof(ServerSockAddr)))
			{
				printf("connect error!\n");
				exit(1);
			}
			
			printf("请输入一个字符串,发送给服务端:");
			gets(Buf);
			/* 发送数据到服务端 */
			send(ClientFd, Buf, strlen(Buf), 0);
			memset(Buf, 0, BUF_LEN);   // 重置缓冲区
			
			/* 接受服务端的返回数据 */
			recv(ClientFd, Buf, BUF_LEN, 0);
			printf("服务端发送过来的数据为:%s\n", Buf);
			
			memset(Buf, 0, BUF_LEN);   // 重置缓冲区
			close(ClientFd);   // 关闭套接字
		}
		return 0;
	}
  • 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
    • Linux 下编译就不需要添加 -lwsock32 参数:
	gcc linux_tcp_server.c -o linux_tcp_server
	gcc linux_tcp_client.c -o linux_tcp_client
  • 1
  • 2
    • 实验现象:
	$ ./linux_tcp_server
	客户端发送过来的数据为:hello
	客户端发送过来的数据为:world

	$ ./linux_tcp_client
	请输入一个字符串,发送给服务端:hello
	服务端发送过来的数据为:hello
	请输入一个字符串,发送给服务端:world
	服务端发送过来的数据为:hello
	请输入一个字符串,发送给服务端:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
    • 在调试这份程序时,出现了绑定错误:
	$ ./linux_tcp_client
	bind error!
  • 1
  • 2
    • 经上网查询发现是端口重复使用,可以在调用 bind() 函数之前调用 setsockopt() 函数以解决端口重复使用的问题:

在这里插入图片描述

② 基于 UDP 的本地客户端、服务端信息交互实例

  • windows 的程序
    • 服务端程序 udp_server.c:
	#include <stdio.h>
	#include <winsock2.h>
	
	#define BUF_LEN  100
	
	int main(void) {
		WSADATA wd;
		SOCKET ServerSock;
		char Buf[BUF_LEN] = {0};
		SOCKADDR ClientAddr;
		SOCKADDR_IN ServerSockAddr;
		int addr_size = 0;
		
		
		/* 初始化操作sock需要的DLL */
		WSAStartup(MAKEWORD(2,2),&wd);  
		
		/* 创建服务端socket */
		if(-1 == (ServerSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)))
		{
			printf("socket error!\n");
			exit(1);
		}
		
		/* 设置服务端信息 */
	    memset(&ServerSockAddr, 0, sizeof(ServerSockAddr)); 	// 给结构体ServerSockAddr清零
	    ServerSockAddr.sin_family = AF_INET;  					// 使用IPv4地址
	    ServerSockAddr.sin_addr.s_addr = htonl(INADDR_ANY); 	// 自动获取IP地址
	    ServerSockAddr.sin_port = htons(1314);  				// 端口
		
		/* 绑定套接字 */
		
	    if (-1 == (bind(ServerSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR))))
		{
			printf("bind error!\n");
			exit(1);
		}
			
		addr_size = sizeof(SOCKADDR);
	
		while (1)
		{
			/* 接受客户端的返回数据 */
			int str_len = recvfrom(ServerSock, Buf, BUF_LEN, 0, &ClientAddr, &addr_size);
			
			printf("客户端发送过来的数据为:%s\n", Buf);
			
			/* 发送数据到客户端 */
			sendto(ServerSock, Buf, str_len, 0, &ClientAddr, addr_size);
			
			/* 清空缓冲区 */
			memset(Buf, 0, BUF_LEN);  
		}
	
		/*如果有退出循环的条件,这里还需要清除对socket库的使用*/
		/* 关闭服务端套接字 */
		//closesocket(ServerSock);
	    /* WSACleanup();*/
	
		return 0;
	}
  • 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
    • 客户端程序 udp_client.c:
	#include <stdio.h>
	#include <winsock2.h>
	
	#define BUF_LEN  100
	
	int main(void) {
		WSADATA wd;
		SOCKET ClientSock;
		char Buf[BUF_LEN] = {0};
		SOCKADDR ServerAddr;
		SOCKADDR_IN  ServerSockAddr;
		int ServerAddrLen = 0;
		
		/* 初始化操作sock需要的DLL */
		WSAStartup(MAKEWORD(2,2),&wd);  
		
		/* 创建客户端socket */
		if (-1 == (ClientSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)))
		{
			printf("socket error!\n");
			exit(1);
		}
		
		/* 向服务器发起请求 */
	    memset(&ServerSockAddr, 0, sizeof(ServerSockAddr));  
	    ServerSockAddr.sin_family = PF_INET;
	    ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
	    ServerSockAddr.sin_port = htons(1314);
		
		ServerAddrLen = sizeof(ServerAddr);
		
		while (1)
		{
			printf("请输入一个字符串,发送给服务端:");
			gets(Buf);
			/* 发送数据到服务端 */
			sendto(ClientSock, Buf, strlen(Buf), 0, (struct sockaddr*)&ServerSockAddr, sizeof(ServerSockAddr));
			
			/* 接受服务端的返回数据 */
			recvfrom(ClientSock, Buf, BUF_LEN, 0, &ServerAddr, &ServerAddrLen);
			printf("服务端发送过来的数据为:%s\n", Buf);
			
			memset(Buf, 0, BUF_LEN);   // 重置缓冲区
		}
		
		closesocket(ClientSock);   // 关闭套接字
		// WSACleanup();  /*如果有退出循环的条件,这里还需要清除对socket库的使用*/
		return 0;
	}
  • 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
  • Linux 下的程序
    • 服务端程序 linux_udp_server.c:
	#include <stdio.h>
	#include <string.h>
	#include <stdlib.h>
	#include <unistd.h>
	#include <arpa/inet.h>
	#include <sys/socket.h>
	#include <netinet/in.h>
	
	#define BUF_LEN  100
	
	int main(void) {
		int ServerFd;
		char Buf[BUF_LEN] = {0};
		struct sockaddr ClientAddr;
		struct sockaddr_in ServerSockAddr;
		int addr_size = 0; 
		int optval = 1; 
		
		/* 创建服务端socket */
		if ( -1 == (ServerFd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)))
		{
			printf("socket error!\n");
			exit(1);
		}
		
		/* 设置服务端信息 */
	    memset(&ServerSockAddr, 0, sizeof(ServerSockAddr)); 	// 给结构体ServerSockAddr清零
	    ServerSockAddr.sin_family = AF_INET;  					// 使用IPv4地址
	    ServerSockAddr.sin_addr.s_addr = htonl(INADDR_ANY); 	// 自动获取IP地址
	    ServerSockAddr.sin_port = htons(1314);  				// 端口
		
		// 设置地址和端口号可以重复使用  
	    if (setsockopt(ServerFd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0)
		{
			printf("setsockopt error!\n");
			exit(1);
		}
		
		/* 绑定操作,绑定前加上上面的socket属性可重复使用地址 */
	    if (-1 == bind(ServerFd, (struct sockaddr*)&ServerSockAddr, sizeof(ServerSockAddr)))
		{
			printf("bind error!\n");
			exit(1);
		}
		
		addr_size = sizeof(ClientAddr);
	
		while (1)
		{
			/* 接受客户端的返回数据 */
			int str_len = recvfrom(ServerFd, Buf, BUF_LEN, 0, &ClientAddr, &addr_size);
			
			printf("客户端发送过来的数据为:%s\n", Buf);
			
			/* 发送数据到客户端 */
			sendto(ServerFd, Buf, str_len, 0, &ClientAddr, addr_size);
			
			/* 清空缓冲区 */
			memset(Buf, 0, BUF_LEN);  
		}
		
		close(ServerFd);
	
		return 0;
	}
  • 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
    • 客户端程序 linux_udp_client.c:
	#include <stdio.h>
	#include <string.h>
	#include <stdlib.h>
	#include <unistd.h>
	#include <arpa/inet.h>
	#include <sys/socket.h>
	
	#define BUF_LEN  100
	
	int main(void) {
		int ClientFd;
		char Buf[BUF_LEN] = {0};
		struct sockaddr ServerAddr;
		int addr_size = 0;
		struct sockaddr_in  ServerSockAddr;
		
		/* 创建客户端socket */
		if (-1 == (ClientFd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)))
		{
			printf("socket error!\n");
			exit(1);
		}
		
		/* 向服务器发起请求 */
	    memset(&ServerSockAddr, 0, sizeof(ServerSockAddr));  
	    ServerSockAddr.sin_family = PF_INET;
	    ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
	    ServerSockAddr.sin_port = htons(1314);
		
		addr_size = sizeof(ServerAddr);
		
		while (1)
		{
			printf("请输入一个字符串,发送给服务端:");
			gets(Buf);
			/* 发送数据到服务端 */
			sendto(ClientFd, Buf, strlen(Buf), 0, (struct sockaddr*)&ServerSockAddr, sizeof(ServerSockAddr));
			
			/* 接受服务端的返回数据 */
			recvfrom(ClientFd, Buf, BUF_LEN, 0, &ServerAddr, &addr_size);
			printf("服务端发送过来的数据为:%s\n", Buf);
			
			memset(Buf, 0, BUF_LEN);   // 重置缓冲区
		}
		
		close(ClientFd);   // 关闭套接字
		
		return 0;
	}
  • 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
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/172129?site
推荐阅读
相关标签
  

闽ICP备14008679号