Linux Kernel TCP/IP Stack — Socket Layer — TCP/UDP Socket 网络编程

举报
云物互联 发表于 2021/08/05 23:13:52 2021/08/05
【摘要】 目录 文章目录 目录TCP/UDP Socket 逻辑架构创建 Socket绑定 Socket请求建立 Socket 连接监听 Socket接受请求关闭连接数据的发送和接收send 发送函数recv 接收函数sendto 发送函数recvfrom 接收函数 TCP Socket 示例UDP Socket 示例 TCP/UDP Socket 逻辑架构 ...

目录

TCP/UDP Socket 逻辑架构

TCP/UDP Socket 网络编程的本质是:运行在 Userpace 的 C Application,通过调用 Socket 框架提供的 System Calls 来完成 TCP/UDP 协议的编程。

在这里插入图片描述

创建 Socket

获得一个 Socket 文件描述符对象,文件描述符是 Linux 操作文件的句柄。在 Linux 中一切皆文件,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:数据传输方式,常用的有面向连接(SOCK_STREAM)方式(即 TCP) 和无连接(SOCK_DGRAM)的方式(即 UDP)。
  • 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

绑定 Socket

将 Socket 与主机中的某个 IP:Port 绑定起来。

int bind(int sock, struct sockaddr *addr, socklen_t addrlen); 

  
 
  • 1
  • sock:Socket 文件描述符。
  • addr:sockaddr 结构体变量的指针。
  • addrlen:addr 变量的大小,可由 sizeof() 计算得出。

将创建的套接字 ServerSock 与本地 IP、端口进行绑定:

int ServerSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

struct sockaddr_in ServerSockAddr;
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); 

bind(ServerSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR));

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

其中 struct sockaddr_in 类型的结构体变量用于保存 IPv4 的 IP 信息。

struct in_addr {
	unsigned long a_addr;
}

struct sockaddr_in {
	unsigned short sin_family; // 地址类型(2B)
	unsigned short int  sin_port; // 端口号(2B)
	struct in_addr sin_addr; // IP 地址(4B)
	unsigned char sin_zero[8]; 	// 填充空间(8B)
}

struct sockaddr { unsigned short  sa_family;	// 地址类型(2B) char sa_data[14];	// 协议地址(14B)
 }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

先初始化 sockaddr_in,再将它强制转化成 sockaddr 来使用,例如 (SOCKADDR*)&ServerSockAddr,这里涉及到了结构体之间的数据类型转换。这两个结构体,长度都为 16 字节,sockaddr_in.sin_family 的数据存入 sockaddr.sa_family,剩下的 14 个字节存入 sockaddr.sa_data,这样在各种操作中可以方便的处理端口号和 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
  • 8

请求建立 Socket 连接

客户端连接到服务端的某个 Socket,所以下述的 struct sockaddr *serv_addr 填充服务端的信息。

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

监听 Socket

服务端进程监听 Socket 是否有新的,由客户端发起的连接请求。

int listen(int sock, int backlog);

  
 
  • 1
  • sock:需要进入监听状态的 Socket。
  • backlog:请求队列的最大长度。

接受请求

服务端接受客户端的连接请求,并返回一个客户端的 Socket 文件描述符,该描述符作为服务端操作对应该客户端的连接的句柄。

int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);

  
 
  • 1
  • sock:服务器端套接字。
  • addr:sockaddr_in 结构体变量。
  • addrlen:addr 的长度,可由 sizeof() 求得。
  • 返回值:一个新的套接字,用于与客户端进行通信。

示例

/* 监听客户端请求,accept() 返回一个新的套接字,发送和接收都是用这个套接字 */
int ClientSock = accept(ServerSock, (SOCKADDR*)&ClientAddr, &len);

  
 
  • 1
  • 2

关闭连接

服务端完成通信任务之后关闭与某个客户端的连接,释放资源。所以 int fd 传入的是某个客户端的 Socket 文件描述符。

int close(int fd);

  
 
  • 1
  • fd:要关闭的文件描述符。

数据的发送和接收

  • read()/write()
  • recv()/send()
  • recvmsg()/sendmsg()
  • recvfrom()/sendto()
  • readv()/writev()

send 发送函数

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

  
 
  • 1
  • sockfd:要发送数据的套接字。
  • buf:保存发送数据的缓冲区地址。
  • len:要发送的数据的字节数。
  • flags:发送数据时的选项,常设为 0。

recv 接收函数

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

  
 
  • 1
  • sockfd:要接收数据的套接字。
  • buf:保存接收数据的缓冲区地址。
  • len:要接收的数据的字节数。
  • flags:接收数据时的选项,常设为 0。

sendto 发送函数

ssize_t sendto(int sock, void *buf, size_t nbytes, int flags, struct sockaddr *to, socklen_t addrlen);

  
 
  • 1
  • sock:用于传输 UDP 数据的套接字;
  • buf:保存待传输数据的缓冲区地址;
  • nbytes:带传输数据的长度(以字节计);
  • flags:可选项参数,若没有可传递 0;
  • to:存有目标地址信息的 sockaddr 结构体变量的地址;
  • addrlen:传递给参数 to 的地址值结构体变量的长度。

recvfrom 接收函数

ssize_t recvfrom(int sock, void *buf, size_t nbytes, int flags, struct sockadr *from, socklen_t *addrlen);

  
 
  • 1
  • sock:用于接收 UDP 数据的套接字;
  • buf:保存接收数据的缓冲区地址;
  • nbytes:可接收的最大字节数(不能超过 buf 缓冲区的大小);
  • flags:可选项参数,若没有可传递 0;
  • from:存有发送端地址信息的 sockaddr 结构体变量的地址;
  • addrlen:保存参数 from 的结构体变量长度的变量地址值。

TCP Socket 示例

在这里插入图片描述

  • 服务端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <errno.h>

#define BUF_LEN 100 /* Size of buffer. */

/* Print exception information. */
#define ERR_MSG(errnum) do { \ errnum = errno; \ fprintf(stderr, "ERROR num: %d\n", errnum); \ perror("PERROR message"); \ fprintf(stderr, "STRERROR message: %s\n", strerror(errnum)); \
} while (0)

extern int errno;


int main(void)
{ int server_fd = 0; int client_fd = 0; char buf[BUF_LEN] = {0}; int addr_len = 0; int recv_len = 0; int optval = 1; struct sockaddr client_addr; memset(&client_addr, 0, sizeof(struct sockaddr)); struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(struct sockaddr)); /* 创建 TCP 服务端 Socket 文件描述符。 */ if (-1 == (server_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))) { printf("socket ERROR.\n"); ERR_MSG(errno); exit(1); } server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY);	// INADDR_ANY,即 0.0.0.0 表示监听本机所有的 IP 地址,在生产环境中不建议使用。 server_addr.sin_port = htons(6666); /* 设置地址和端口号可以重复使用,回避了端口可能冲突的问题,在生产环境中不建议使用。*/ if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0) { printf("setsockopt ERROR.\n"); ERR_MSG(errno); exit(1); } if (-1 == (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(struct sockaddr)))) { printf("bind ERROR.\n"); ERR_MSG(errno); exit(1); } if (-1 == (listen(server_fd, 10))) { printf("listen ERROR.\n"); ERR_MSG(errno); exit(1); } addr_len = sizeof(struct sockaddr); while (1) { /* 监听某个客户端的连接请求,返回客户端 Socket 文件描述符,对该客户端的发送和接收都使用这个套接字。 */ if (-1 == (client_fd = (accept(server_fd, (struct sockaddr*)&client_addr, &addr_len)))) { printf("accept ERROR.\n"); ERR_MSG(errno); exit(1); } if ((recv_len = recv(client_fd, buf, BUF_LEN, 0)) < 0) { printf("recv ERROR.\n"); ERR_MSG(errno); exit(1); } printf("Client sent data %s\n", buf); send(client_fd, buf, recv_len, 0); /* 关闭套接字。 */ close(client_fd); 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
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 客户端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <errno.h>


#define BUF_LEN 100 /* Size of buffer. */

/* Print exception information. */
#define ERR_MSG(errnum) do { \ errnum = errno; \ fprintf(stderr, "ERROR num: %d\n", errnum); \ perror("PERROR message"); \ fprintf(stderr, "STRERROR message: %s\n", strerror(errnum)); \
} while (0)

extern int errno;


int main(void)
{ int client_fd; char buf[BUF_LEN] = {0}; struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(struct sockaddr)); /* 连接到指定的服务端。 */ server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); server_addr.sin_port = htons(6666); while (1) { if (-1 == (client_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))) { printf("socket ERROR.\n"); ERR_MSG(errno); exit(1); } /* 向指定的服务端发出连接请求。 */ if (-1 == (connect(client_fd, (struct sockaddr*)&server_addr, sizeof(struct sockaddr)))) { printf("connect ERROR.\n"); ERR_MSG(errno); exit(1); } printf("Send to client >"); gets(buf); send(client_fd, buf, BUF_LEN, 0); memset(buf, 0, BUF_LEN); recv(client_fd, buf, BUF_LEN, 0); printf("Receive from server: %s\n", buf); memset(buf, 0, BUF_LEN); close(client_fd); } 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

编译:

gcc tcp_server.c -o tcp_server
gcc tcp_client.c -o tcp_client

  
 
  • 1
  • 2

运行:

  1. 先启动 TCP Server:
# ./tcp_server

  
 
  • 1
  1. 查看监听 Socket 是否绑定成功:
$ netstat -lpntu | grep 6666
tcp 0 0 0.0.0.0:6666 0.0.0.0:* LISTEN 28675/./tcp_server

  
 
  • 1
  • 2
  1. 启动 TCP Client
# ./tcp_client

  
 
  • 1

在这里插入图片描述

UDP Socket 示例

·

  • 服务端
#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; /* 创建 UDP 服务端 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
  • 66
  • 客户端
#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
  • 50

运行:

$ netstat -lpntu | grep 1314
udp 0 0 0.0.0.0:1314 0.0.0.0:* 29729/./udp_server

  
 
  • 1
  • 2

文章来源: is-cloud.blog.csdn.net,作者:范桂飓,版权归原作者所有,如需转载,请联系作者。

原文链接:is-cloud.blog.csdn.net/article/details/106962818

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。