Linux套接字Socket
前提知识补充
为不同机器上的两个进程之间提供通信机制
主机字节序小端存储,网络字节序大端存储
特点 TCP UDP 连接类型 面向连接 无连接 可靠性 高 低 有序性 保证数据包按顺序到达 不保证数据包顺序 流量控制 有滑动窗口机制 无 拥塞控制 有拥塞控制机制 无 复杂性 较高 较低 实时性 较差 较好 适用场景 文件传输、Web 应用、电子邮件等 实时音视频、在线游戏、DNS 查询等
Socket
前情提示
inet_addr函数
- 将点分十进制格式的 IPv4 地址字符串转换为网络字节序的 32 位无符号整数
接口代码
#include <arpa/inet.h>in_addr_t inet_addr(const char* ip_address);
参数解释
- ip_address: 点分十进制IPV4
返回值
- 网络字节序的 32 位无符号整数
实例代码
#include <stdio.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>int main() {// 定义IPV4地址const char *ip_address = "192.168.1.1";in_addr_t ip;// 将点分十进制格式的 IP 地址转换为 32 位无符号整数ip = inet_addr(ip_address);if (ip == INADDR_NONE) {perror("inet_addr");printf("无效的 IP 地址: %s\n", ip_address);return 1;}// 打印转换后的 IP 地址printf("IP 地址: %s\n", ip_address);printf("转换后的 32 位无符号整数: 0x%08X\n", (unsigned int)ip);return 0;
}
套接字结构定义
#include <netine/in.h>struct sockaddr_in
{// 地址族,对于IPV4地址应设置为 AF_INETsa_family_t sin_family;// 16位端口号,以大端字节序存储in_port_t sin_port;// 32位IPV4地址,以大端字节序存储,指明通信所使用的IP地址/*** struct in_addr {* in_addr_t s_addr; /* 32 位无符号整数,表示 IP 地址 */* };*/struct in_addr sin_addr;// 填充字段,一般设置为0unsigned char sin_zero[8]
}
字节序
- 将16位和32位整数在主机字节序和网络字节序之间进行转换
接口代码
#include <netinet/in.h>// h:hostname -- 本地
// to:to -- 转变
// n:network -- 网络
// l:32位
// s:16位
unsigned long int htonl(unsigned long int hostlong);
unsigned short int htons(unsigned short int hostshort);
unsigned long int ntohl(unsigned short int netlong);
unsigned short int ntohs(unsigned short int netshort);
gethostname函数
- 获取主机名
接口代码
#include <unistd.h>int gethostname(char* name,int namelength);
参数解释
- name: 自定义缓冲区
- namelength: 自定义缓冲区大小
返回值
- 成功时返回 0。
- 失败时返回 -1,并设置
errno
以指示错误原因
实例代码
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>int main()
{char buf[256];int res;res = gethostname(buf, sizeof(buf));if (res == 0)printf("本机地址为: %s\n", buf);return 0;
}
地址转换打印函数inet_ntoa
- 将网络字节转换位可打印四点表示法格式的字符串
接口代码
#include <arpa/inet.h>char* inet_ntoa(struct in_addr in);
参数解释
- in: 32位无符号整数
返回值
- 成功时,返回一个指向静态分配的字符数组的指针,该数组包含点分十进制格式的 IP 地址字符串。
- 如果输入无效,则返回
NULL
实例代码
#include <stdio.h>
#include <arpa/inet.h>
#include <string.h>int main()
{struct in_addr addre;addre.s_addr = inet_addr("192.168.1.1");char *buf;buf = inet_ntoa(addre);printf("四点表示法字符串为: %s\n", buf);return 0;
}
服务器端and客户端
TCP执行流程
UDP执行流程
socket(创建)
- 创建通信连接句柄
接口代码
#include <sys/socket.h>int socket(int domain,int type,int protocol);
参数解释
-
domain:指定通信域(也称为地址族),决定了将使用的寻址方案。常见的值包括:
AF_INET
:IPv4 网络协议。AF_INET6
:IPv6 网络协议。AF_UNIX
或AF_LOCAL
:本地通信(同一台机器上的进程间通信)。
-
type:指定套接字的类型,决定了通信方式。常见的值包括:
-
SOCK_STREAM
:提供有序的、可靠的、基于连接的字节流服务,通常使用 TCP 协议。 -
SOCK_DGRAM
:提供无连接的、不可靠的数据报服务,通常使用 UDP 协议。 -
SOCK_RAW
:提供原始网络协议访问,通常用于底层网络编程。
-
-
protocol
:指定具体的协议。大多数情况下,可以设置为 0。
返回值
- 成功时,返回一个非负整数,即新创建的套接字的文件描述符。
- 失败时,返回 -1,并设置
errno
以指示错误原因。
实例代码
// 参考下方汇总
bind(绑定)
- 把电脑上真正的网络地址与一个套接字标识符关联起来
接口代码
#include <sys/types.h>
#include <sys/socket.h>int bind(int sockfd,const struct sockaddr* address,size_t addrlen);
参数解释
- sockfd: 要绑定的套接字描述符
- address: 要绑定的地址信息
- addrlen: address接口体大小
返回值
- 成功时,返回 0
- 失败时,返回 -1,并设置
errno
以指示错误原因
实例代码
// 参考下方汇总
listen(监听)
- 绑定之后,在任何客户端系统可以连接到新建立的服务器端点之前,服务器必须设定为等待连接
接口代码
#include <sys/socket.h>int listen(int sockfd,int backlog);
参数解释
sockfd
:要设置为监听状态的套接字描述符。backlog
:指定等待连接的最大队列长度。这是指尚未被accept
接受但已经完成三次握手的连接请求的数量
返回值
- 成功时,返回 0。
- 失败时,返回 -1,并设置
errno
以指示错误原因。
实例代码
// 参考下方汇总
accept(接受连接)
- 当服务器收到客户端connect请求时,必须建立一个全新的套接字来处理这个特定的通信。第一个套接字只用来建立通信,第二个套接字由accept完成
接口代码
#include <sys/types.h>
#include <sys/socket.h>int accept(int sockfd,struct sockaddr* address,size_t* add_len);
参数解释
- sockfd: 处于监听状态的套接字
- address: 当服务器调用
accept
函数接受一个新的连接请求时,address
会被填充为连接到服务器的客户端的地址信息 - address: 指定address缓冲区大小,并在返回时更新实际存储长度
返回值
- 成功时,返回一个新的套接字描述符,该描述符用于与客户端进行通信。
- 失败时,返回 -1,并设置
errno
以指示错误原因。
实例代码
// 参考下方汇总
connect(请求连接)
- 客户程序通过一个未命名套接字和服务器监听套接字之间建立连接的方法来连接到服务器
接口代码
#include <sys/socket.h>int connect(int sockfd,const struct sockaddr* addr,socklen_t addrlen);
参数解释
- sockfd: 指定的套接字将连接到参数address指定服务器套接字
- addr: 目的地址,IPV4或IPV6
- addrlen: addr结构体大小
返回值
- 成功时,返回 0。
- 失败时,返回 -1,并设置
errno
以指示错误原因。
实例代码
// 参考下方汇总
recv(接收数据)
- 从指定的文件描述符读取数据
接口代码
#include <sys/types.h>
#include <sys/socket.h>ssize_t recv(int sockfd,void* buffer,size_t length,int flags);
参数解释
sockfd
:要从中接收数据的套接字描述符。buf
:指向一个缓冲区的指针,用于存储接收到的数据。len
:缓冲区的大小(以字节为单位)。flags
:指定调用的行为选项。常见的标志包括:0
:默认行为,阻塞直到有数据可读或发生错误。MSG_PEEK
:查看传入的数据而不从输入队列中移除。MSG_DONTWAIT
:非阻塞模式,如果当前没有数据可读,则立即返回-1
并设置errno
为EAGAIN
或EWOULDBLOCK
。MSG_WAITALL
:阻塞直到至少len
字节的数据被接收,或者发生错误
返回值
- 成功时,返回实际接收到的字节数(可能少于请求的
len
)。 - 如果连接关闭且所有数据已被读取,返回 0。
- 失败时,返回 -1,并设置
errno
以指示错误原因。
实例代码
// 参考下方汇总
send(发送数据)
- 向指定套接字发送数据
接口代码
#include <sys/types.h>
#include <sys/socket.h>ssize_t send(int sockfd,const void* buffer,size_length len,int flags);
参数解释
ockfd
:要发送数据的套接字描述符。buf
:指向包含要发送数据的缓冲区的指针。len
:要发送的数据长度(以字节为单位)。flags
:指定调用的行为选项。常见的标志包括:0
:默认行为,阻塞直到所有数据被发送或发生错误。MSG_DONTWAIT
:非阻塞模式,如果当前无法立即发送数据,则立即返回-1
并设置errno
为EAGAIN
或EWOULDBLOCK
。MSG_NOSIGNAL
:防止 SIGPIPE 信号,当对端已经关闭连接时,不会产生 SIGPIPE 信号。MSG_OOB
:发送带外数据(out-of-band data)。
返回值
- 成功时,返回实际发送的字节数(可能少于请求的
len
)。 - 如果连接关闭且所有数据已被发送,返回 0。
- 失败时,返回 -1,并设置
errno
以指示错误原因。
实例代码
// 参考下方汇总
close(关闭)
- 通过文件描述符关闭套接字
接口代码
#include <unistd.h>int close(int fd);
参数解释
fd
:要关闭的文件描述符。
返回值
- 成功时,返回 0。
- 失败时,返回 -1,并设置
errno
以指示错误原因。
实例代码
// 参考下方汇总
recvfrom(接收)
- UDP接收函数
接口代码
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>ssize_t revcfrom(int sockfd,void* message,size_t length,int flags,struct sockaddr* send_add,socklen_t* add_len);
参数解释
- sockfd: Socket创建文件描述符
- message: 存储接收数据
- length:
message
缓冲区的最大长度 - flags: 用于修改
revcfrom
的行为MSG_PEEK
:查看传入的数据,但不将其从输入队列中移除。MSG_WAITALL
:等待接收指定数量的字节,除非发生错误或信号中断。MSG_DONTWAIT
:使recvfrom
在非阻塞模式下操作,如果没有数据可读,则立即返回-1
并设置errno
为EAGAIN
或EWOULDBLOCK
。
- send_addr: 存储发送方的地址信息
- 当为
NULL
时雨recv
工作方式相同
- 当为
- add_len:
send_addr
的实际大小
返回值
- 成功:返回接收到的字节数(可能小于
length
)。 - 失败:返回
-1
,并设置errno
以指示错误类型。
实例代码
// 参考下方汇总
sendto(发送)
- UDP发送函数
接口代码
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>ssize_t sendto(int sockfd, const void *message, size_t length, int flags,const struct sockaddr *dest_addr, socklen_t dest_len);
参数解释
- sockfd: Socket创建文件描述符
- message: 要发送的信息
- length: 要发送信息的长度
- flags: 用于修改
sendto
MSG_CONFIRM
:请求确认。MSG_DONTROUTE
:不要路由,直接发送到本地网络。MSG_EOR
:记录结束。MSG_OOB
:带外数据。MSG_NOSIGNAL
:防止 SIGPIPE 信号。
- dest_addr: 指向发送到目标地址的信息
- dest_len:
dest_addr
结构体大小
返回值
- 成功:返回发送的字节数(可能小于
length
)。 - 失败:返回
-1
,并设置errno
以指示错误类型。
实例代码
// 见下方汇总
综合实例–TCP连接
server-tcp.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>int port = 7777; // 端口号
int main()
{char buf[4096]; // 接收服务器端返回信息缓冲区int socketDes;int addLen; // 接收连接时地址信息长度// 创建套接字文件描述符socketDes = socket(AF_INET, SOCK_STREAM, 0);// 地址信息struct sockaddr_in sin;bzero(&sin, sizeof(sin));sin.sin_family = AF_INET;/*** INADDR_ANY 是一个常量,值为 0x00000000,表示绑定到所有可用的网络接口。* 这意味着服务器将监听所有可用的网络接口上的指定端口*/sin.sin_addr.s_addr = INADDR_ANY;sin.sin_port = htons(port); // 监听端口// 绑定int bindRes;bindRes = bind(socketDes, (struct sockaddr *)&sin, sizeof(sin));if (bindRes == -1){printf("bind--");exit(1);}// 监听int listenRes;listenRes = listen(socketDes, 20);if (listenRes == -1){perror("listen--");exit(1);}while (1){struct sockaddr_in pin;int temp_sock_descriptor;temp_sock_descriptor = accept(socketDes, (struct sockaddr *)&pin, &addLen);// 打印客户端信息printf("Connection accepted from %s:%d\n", inet_ntoa(pin.sin_addr), ntohs(pin.sin_port));if (temp_sock_descriptor == -1){perror("accept--");exit(1);}size_t len;len = recv(temp_sock_descriptor, buf, sizeof(buf), 0);if (len == -1){perror("recv--");exit(1);}printf("喜羊羊: %s\n", buf);// 示例:向客户端发送一条欢迎消息const char *sendMessage = "Welcome to the server!\n";if (strcmp(buf, "你好") == 0)sendMessage = "你也好";send(temp_sock_descriptor, sendMessage, strlen(sendMessage), 0); // 发送数据memset(buf, 0, 4096); // 清空数组close(temp_sock_descriptor); // 关闭套接字}return 0;
}
client-tcp.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>// 服务器地址
const char *hostname = "127.0.0.1";
int port = 7777;int main(int argc, char *argv[])
{char buf[4096];int socketDes;char *message;if (argc < 2){printf("发送信息不能为空\n");exit(1);}message = argv[1];// 创建套接字文件描述符socketDes = socket(AF_INET, SOCK_STREAM, 0);if (socketDes == -1){perror("socket--");exit(1);}// 处理对方IP数据struct hostent *server_host_name;server_host_name = gethostbyname(hostname);if (server_host_name == 0){perror("接受失败--");exit(1);}struct sockaddr_in pin;bzero(&pin, sizeof(pin));pin.sin_family = AF_INET;pin.sin_addr.s_addr = ((struct in_addr *)(server_host_name->h_addr))->s_addr;pin.sin_port = htons(port);// 请求连接int res;res = connect(socketDes, (void *)&pin, sizeof(pin));if (res == -1){perror("res--");exit(1);}// 发送数据ssize_t len;len = send(socketDes, message, sizeof(message), 0);// 接收数据ssize_t reveLen;reveLen = recv(socketDes, buf, sizeof(buf), 0);printf("灰太狼: %s\n", buf);// 关闭close(socketDes);exit(0);
}
综合实力–UDP连接
server-udp.c
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/time.h>int main(int argc, char *argv[])
{int socketDes;socketDes = socket(AF_INET, SOCK_DGRAM, 0);if (socketDes < 0){printf("文件描述符创建失败\n");exit(1);}struct sockaddr_in servAddr;struct sockaddr_in clientAddr;servAddr.sin_family = AF_INET;servAddr.sin_addr.s_addr = htonl(INADDR_ANY);servAddr.sin_port = htons(7777);int rc = bind(socketDes, (struct sockaddr *)&servAddr, sizeof(servAddr));if (rc < 0){printf("绑定失败\n");exit(1);}printf("等待接收信息\n");char msg[50];int cliLen, n;while (1){memset(msg, 0x0, 50);cliLen = sizeof(clientAddr);n = recvfrom(socketDes, msg, 50, 0, (struct sockaddr *)&clientAddr, &cliLen);if (n < 0){printf("信息接受失败\n");continue;}printf("%s: from %s:UDP %u : %s\n", argv[0], inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port), msg);int sendToRes;char *res = "你也ggg";sendToRes = sendto(socketDes, res, sizeof(res), 0, (struct sockaddr *)&clientAddr, sizeof(clientAddr));if (sendToRes < 0){printf("返回消息失败\n");exit(1);}}exit(0);
}
client-udp.c
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/time.h>int port = 7777;int main(int argc, char *argv[])
{int socketDes;char buf[4096];struct sockaddr_in cliAddr, remoteServAddr;struct hostent *h;if (argc < 3){printf("请输入发送目标地址和发送信息\n");exit(1);}h = gethostbyname(argv[1]);if (h == NULL){printf("%s: 无法识别地址 %s\n", argv[0], argv[1]);exit(1);}remoteServAddr.sin_family = h->h_addrtype;memcpy((char *)&remoteServAddr.sin_addr.s_addr, h->h_addr_list[0], h->h_length);remoteServAddr.sin_port = htons(port);// 建立套接字文件描述符socketDes = socket(AF_INET, SOCK_DGRAM, 0);if (socketDes < 0){printf("无法打开文件描述符\n");exit(1);}cliAddr.sin_family = AF_INET;// 客户端可以绑定到任何可用的网络接口cliAddr.sin_addr.s_addr = htonl(INADDR_ANY);// 系统自动分配一个可用的端口cliAddr.sin_port = htons(0);int bindRes;bindRes = bind(socketDes, (struct sockaddr *)&cliAddr, sizeof(cliAddr));if (bindRes < 0){printf("绑定失败\n");return 0;}int sendToRes;for (int i = 2; i < argc; i++){sendToRes = sendto(socketDes, argv[i], strlen(argv[i]) + 1, 0, (struct sockaddr *)&remoteServAddr, sizeof(remoteServAddr));if (sendToRes < 0){printf("发送失败\n");close(socketDes);exit(1);}}char msg[50];int cliLen = sizeof(remoteServAddr);int n = recvfrom(socketDes, msg, 50, 0, (struct sockaddr *)&remoteServAddr, &cliLen);if (n < 0){printf("接收信息失败\n");exit(1);}printf("接收到的数据为:%s\n", msg);exit(0);
}