目录

文章目录

  • 目录
  • TCP/UDP Socket 逻辑架构
  • 创建 Socket
  • 绑定 Socket
  • 请求建立 Socket 连接
  • 监听 Socket
  • 接受请求
  • 关闭连接
  • 数据的发送和接收
    • send 发送函数
    • recv 接收函数
    • sendto 发送函数
    • recvfrom 接收函数
  • TCP Socket 示例
  • 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);
  • 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);

创建 UDP 套接字:

int udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

绑定 Socket

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

int bind(int sock, struct sockaddr *addr, socklen_t addrlen);
  • 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));

其中 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)}

先初始化 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_INET6in_port_t sin6_port;        // 16 位端口号uint32_t sin6_flowinfo;     // IPv6 流信息struct in6_addr sin6_addr;  // 具体的 IPv6 地址uint32_t sin6_scope_id;     // 接口范围 ID
};

请求建立 Socket 连接

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

int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen);

示例

int ClientSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
connect(ClientSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR));

监听 Socket

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

int listen(int sock, int backlog);
  • sock:需要进入监听状态的 Socket。
  • backlog:请求队列的最大长度。

接受请求

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

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

示例

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

关闭连接

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

int close(int fd);
  • 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);
  • sockfd:要发送数据的套接字。
  • buf:保存发送数据的缓冲区地址。
  • len:要发送的数据的字节数。
  • flags:发送数据时的选项,常设为 0。

recv 接收函数

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
  • sockfd:要接收数据的套接字。
  • buf:保存接收数据的缓冲区地址。
  • len:要接收的数据的字节数。
  • flags:接收数据时的选项,常设为 0。

sendto 发送函数

ssize_t sendto(int sock, void *buf, size_t nbytes, int flags, struct sockaddr *to, socklen_t addrlen);
  • 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);
  • 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;
}
  • 客户端
#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;
}

编译:

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

运行:

  1. 先启动 TCP Server:
# ./tcp_server
  1. 查看监听 Socket 是否绑定成功:
$ netstat -lpntu | grep 6666
tcp        0      0 0.0.0.0:6666            0.0.0.0:*               LISTEN      28675/./tcp_server
  1. 启动 TCP Client
# ./tcp_client

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  100int 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;
}
  • 客户端
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>#define BUF_LEN  100int 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;
}

运行:

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

Linux Kernel TCP/IP Stack — Socket Layer — TCP/UDP Socket 网络编程相关推荐

  1. Linux Kernel TCP/IP Stack — L3 Layer — netfilter 框架

    目录 文章目录 目录 netfilter 框架 netfilter 的组成模块 netfilter 的 Hook 机制实现 netfilter 的工作原理 规则(Rules) 链(Chains) 表( ...

  2. Linux Kernel TCP/IP Stack — L7 Layer — Application Socket I/O 接口类型

    目录 文章目录 目录 基本概念 同步与异步 阻塞与非阻塞 I/O 操作的执行流程 Socket I/O 接口类型 阻塞 IO 缺点 非阻塞 IO 缺点 阻塞 IO 与非阻塞 IO 的区别 IO 多路复 ...

  3. Linux Kernel TCP/IP Stack — L1 Layer — 多队列网卡

    目录 文章目录 目录 多队列网卡 Intel 82575 的多队列硬件实现 Intel 82575 的多队列软件驱动实现 多队列网卡识别 多队列网卡 多队列网卡,是一种用来解决网络 I/O QoS 问 ...

  4. Linux Kernel TCP/IP Stack — L2 Layer — Linux Bridge(虚拟网桥)的基本操作

    目录 文章目录 目录 Linux bridge 的基本操作 创建 Bridge 将 veth pair 连上 Bridge 为 Bridge 配置 IP 地址 将物理网卡接口设备挂靠 Bridge L ...

  5. Linux Kernel TCP/IP Stack — L3 Layer — netfilter 框架 — iptables NAPT 网络地址/端口转换

    目录 文章目录 目录 网络地址转换(NAT) SNAT DNAT 端口映射(PAT) 网络地址转换(NAT) IP 网络有公网与私网的区分,通常内网使用私网 IP,Internet 使用公网 IP,而 ...

  6. Linux Kernel TCP/IP Stack — L1 Layer — tcpdump 抓包

    目录 文章目录 目录 tcpdump CLI 关键字 常用指令选项 常规操作示例 查看哪些接口可用于捕获 过滤主机 过滤端口 过滤网络(网段) 过滤协议 复杂的逻辑表达式过滤条件 检查数据包内容 输出 ...

  7. Linux Kernel TCP/IP Stack — L3 Layer — 路由器子系统

    目录 文章目录 目录 Linux 作为一个路由器 路由表项的类型 route 指令 ip route 指令 添加默认路由 添加静态路由 删除静态路由 操作示例 Linux Kernel 路由子系统 路 ...

  8. Linux Kernel TCP/IP Stack — L3 Layer — netfilter/iptables 防火墙

    目录 文章目录 目录 iptables/netfilter 框架 iptables-service iptables 指令应用 查看规则 添加规则 删除规则 修改规则 保存和加载规则 常规初始化配置 ...

  9. Linux Kernel TCP/IP Stack — L2 Layer — Linux Bridge(虚拟网桥)

    目录 文章目录 目录 Linux bridge Linux bridge 的实现方式 Linux bridge 的代码逻辑 Linux bridge 在 Linux 的语境中,Bridge(网桥)和 ...

  10. Linux Kernel TCP/IP Stack — L2 Layer — Traffic Control(流量控制)的实现原理

    目录 文章目录 目录 基本概念 QoS.Bandwidth 和 Traffic Control 队列 FIFO 队列 pfifo_fast 队列 SFQ 队列 令牌桶队列 数据流(Data Flow) ...

最新文章

  1. Ubuntu更新源问题终于解决了
  2. Django rest_framework 实用技巧
  3. python 去除字符串的标点符号 用_7步搞定数据清洗-Python数据清洗指南
  4. 使用绘图API自定义组件
  5. Codeforces Round #420 (Div. 2)
  6. 程序员在未来会变成廉价劳动力(农民工)吗?
  7. java复习2(编码)
  8. Celery框架简单实例
  9. dp 1.4协议_浅析关于HDMI接口与DP接口
  10. Idea和redis的坑
  11. Java中遍历文件夹的2种方法
  12. 好用的chrome插件总结
  13. k8s学习:挂载 pvc
  14. 2.数据中台 --- 什么是数据中台
  15. LVS+KeepAlived,RabbitMQ高可用负载均衡
  16. adventureworks mysql_AdventureWorks 示例数据库
  17. 代码源每日一题-宝箱(贪心/思维)
  18. 目前几种常见穿NAT的方法分析
  19. linux修改系统时区为上海
  20. 什么是宏任务与微任务?

热门文章

  1. iOS Sprite Kit教程之场景的切换
  2. sqlite导入 mysql_Sqlite向MySql导入数据
  3. fanuc机器人cm格式文件_了解发那科智能机器人自动化物流拆垛
  4. 第二次打开不是最大_“相亲失败,也许不是坏事”
  5. c语言编译时字符黑色,C语言黑与白问题
  6. HoloLens开发入门
  7. 这家刚拿了1亿美元的基金会,要证明“21世纪是生物的世纪”
  8. 美国火星车失联的7分钟,高清视频传回来了
  9. 解放程序员双手!GPT-3自动生成SQL语句 | 代码开源
  10. 北区首届科技文化潮流节启幕!诚邀科技企业参展,共同打造海淀北部最大科技“party”...