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   SocialistYouth  阅读(82)  评论(0编辑  收藏  举报

编辑推荐:
· 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训练数据并当服务器共享给他人

统计

点击右上角即可分享
微信分享提示