Linux 网络编程——Socket编程基础
1.什么是socket?
2.socket基本操作
需要的头文件
#include <unistd.h> #include <sys/types.h> #include <sys/socket.h>
2.1 socket()
int socket(int domain, int type, int protocol);
DESCRIPTION
- domain: 协议族,常见的协议族AF_INET, AF_INET6等
- type: 指定socket类型,SOCK_STREAM(提供有序,可靠,双向的基于连接的字节流), SOCK_DGRAM(支持数据报(固定最大长度的无连接、不可靠消息))等
- protocol: 指定协议, 常用的协议有IPPROTO_TCP、IPPTOTO_UDP等
注意:type和protocol不能随意组合,例如: SOCK_STREAM不可以和IPPTOTO_UDP组合,当protocol为0时,会自动选择type类型对应的默认协议.
2.2 bind()
当一个socket被socket() 创建出后,它存在于命名空间(address family)中,但是没有一个具体的地址。bind() 将 addr 指定的地址分配给文件描述符 sockfd 引用的套接字。如果没有绑定, 在后面调用connect(), listen()时, 系统会自动随机分配一个端口。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
DESCRIPTION
- sockfd: socket描述符,由socket()唯一创建
- addr: const struct sockaddr *的指针,指向绑定给sockfd的协议地址,由于一些历史问题,通常该指针指向一个sockaddr_in类型的结构体(IPv4)
- addrlen: 指定 addr 指向的地址结构的大小(以字节为单位)
struct sockaddr_in { sa_family_t sin_family; /* address family: AF_INET */ in_port_t sin_port; /* port in network byte order */ struct in_addr sin_addr; /* internet address */ }; /* Internet address. */ struct in_addr { uint32_t s_addr; /* address in network byte order */ };
网络字节序和主机字节序(在另一篇文章详细讲解)
数据在传输过程中,例如从主机a到主机b进行传输信息,会经历以下三步:
a的主机字节序->网络字节序-->b的主机字节序
主机字节序 :主机内部,内存中数据的存储方式,引用标准的Big_Endian(大端)和Little_Endian(小端)的定义如下:
(1) Big_Endian: 高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
(2) Little_Endian: 低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
网络字节序 :网络字节顺序是TCP/IP中规定好的一种数据表示格式,采用Big-endian(大端)排序方式。
因此,在将sockaddr_in地址绑定给sockfd前,如果主机字节序是小端存储,请先将主机字节序转化为网络字节序.
通常服务器会在listen()之前,通过bind()绑定一个暴露给客户端的地址(IP地址+端口号),客户端可以通过这个暴露的地址建立连接获取服务;而客户端不需要通过bind()确定一个指定地址,而是在connet()时候系统随机分配一个
2.3 listen()/connect()
int listen(int sockfd, int backlog);
DESCRIPTION
- sockfd: 文件描述符,它引用 SOCK_STREAM 或 SOCK_DGRAM 类型的套接字。
- backlog: backlog定义了 sockfd 的挂起连接队列可以增长到的最大长度。如果连接请求在队列已满时到达,客户端可能会收到带有 ECONNREFUSED 指示的错误,或者,如果底层协议支持重传,则可能会忽略该请求,以便稍后重新尝试连接成功。
作为Server,在调用了bind()函数后,就应该对sockfd进行监听,listen()函数将默认主动的socket变为被动类型的,等待客户端的连接请求。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
DESCRIPTION
- sockfd: 客户端的socket描述符
- addr: 服务器的socket地址
- addrlen: socket地址的长度
客户端通过connect()函数与服务器建立TCP连接
2.4 accept()
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
DESCRIPTION
- 和bind()参数一致
侦听套接字提取挂起连接队列中的第一个连接请求,sockfd,创建一个新的连接套接字,并返回一个引用该套接字的新文件描述符。 新创建的套接字不处于监听状态。 原始套接字 sockfd 不受此调用的影响。
RETURN VALUE
- 成功时,这些系统调用返回一个非负整数,它是接受套接字的文件描述符。
- 出错时,返回 -1,适当设置 errno,并且 addrlen 保持不变。
TCP服务器端依次调用socket()、bind()、listen()之后,就会监听指定的socket地址了。TCP客户端依次调用socket()、connect()之后就向TCP服务器发送了一个连接请求。TCP服务器监听到这个请求之后,就会调用accept()函数取接收请求,这样连接就建立好了。之后就可以开始网络I/O操作了,即类同于普通文件的读写I/O操作。
2.5 send(),recv()等I/O函数
ssize_t send(int sockfd, const void *buf, size_t len, int flags); // 只能在套接字处于连接状态时使用(以便知道预期的接收者)
DESCRIPTION
- sockfd: 要发送给的哪个socket描述符
- buf: 发送的消息
- len: 消息大小,如果消息太长而无法原子地通过底层协议,则返回错误 EMSGSIZE,并且不传输消息。
- flags: 标识参数,使用0标识参数,send()等效于write()
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
DESCRIPTION
- 参数和send()一致
2.6 close()
int close(int fd);
关闭文件描述符,使其不再引用任何文件并且可以重用。
2.socket编程demo
- Server.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> // socket套接字 #include <unistd.h> #include <arpa/inet.h> // 主机字节序和网络字节序转化 #define _SERVER_PORT 8080 #define BACK_LOG 128 #define IP_SIZE 16 #define BUFFER_SIZE 1600 int server_net_init(void) { struct sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_port = htons(_SERVER_PORT); server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //设置本机任意IP int server_fd = socket(AF_INET, SOCK_STREAM, 0); if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) { perror("bind error"); exit(-1); } listen(server_fd, BACK_LOG); // 将默认主动的socket变为被动类型的 printf("TCP Server Waiting for Connect ...\n"); return server_fd; } int server_accepting(int sockfd) { // 阻塞模型,阻塞等待客户端连接 struct sockaddr_in client_addr; socklen_t addrlen = sizeof(client_addr); int client_fd; char client_ip[IP_SIZE]; bzero(client_ip, IP_SIZE); if ((client_fd = accept(sockfd, (struct sockaddr *)&client_addr, &addrlen)) > 0) { // accept 阻塞函数 printf("TCP Server Accept Success:client ip[%s] client prot[%d] \n", inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, client_ip, IP_SIZE),ntohs(client_addr.sin_port)); } else { perror("Accept Call Failed"); exit(-1); } return client_fd; } int server_recv_response(int sockfd) { char recv_buffer[BUFFER_SIZE]; bzero(recv_buffer, BUFFER_SIZE); ssize_t recvlen; while ((recvlen = recv(sockfd, recv_buffer, sizeof(recv_buffer), 0)) > 0) { printf("%s", recv_buffer); send(sockfd, recv_buffer, recvlen, 0);// 回传 bzero(recv_buffer, BUFFER_SIZE); } if (recvlen == -1) { printf("recv error"); exit(-1); } return 0; } int main(void) { int sfd = server_net_init(); // 服务端网络初始化 int cfd = server_accepting(sfd); // 阻塞等到连接 server_recv_response(cfd); close(cfd); close(sfd); return 0; }
- Client.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> // socket套接字 #include <unistd.h> #include <arpa/inet.h> // 大小端转换 #define _SERVER_PORT 8080 #define _SERVER_IP "172.24.126.207" #define BUFFER_SIZE 1600 int main(void) { struct sockaddr_in server_addr; bzero(&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(_SERVER_PORT); inet_pton(AF_INET, _SERVER_IP,&server_addr.sin_addr.s_addr); // 设置服务器IP int cfd = socket(AF_INET, SOCK_STREAM, 0); int reval; ssize_t recvlen; char buffer[BUFFER_SIZE]; bzero(buffer, BUFFER_SIZE); if ((reval = connect(cfd, (struct sockaddr *)&server_addr, sizeof(server_addr))) == 0) { printf("Client Connection Server Success ... \n"); while ((fgets(buffer, sizeof(buffer), stdin)) != NULL) { send(cfd, buffer, strlen(buffer), 0); recvlen = recv(cfd, buffer, sizeof(buffer), 0); write(STDOUT_FILENO, buffer, recvlen); bzero(buffer, BUFFER_SIZE); } } else if (reval == -1) { perror("Connection Failed"); exit(-1); } return 0; }
posted on 2022-04-27 17:48 SocialistYouth 阅读(82) 评论(0) 编辑 收藏 举报
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人