1587 字
8 分钟
网络套接字:流式套接字&TCP【Unix编程】
流式套接字(Stream Socket)用于在网络应用程序之间进行可靠的、基于连接的通信。它通常使用TCP(Transmission Control Protocol)作为传输协议,因此提供了数据传输的可靠性和顺序保证。在流式套接字中,数据以字节流的形式发送和接收,双方可以确定连接的建立和断开。
使用流程
Server端流程:
- 创建套接字:使用
socket(AF_INET, SOCK_STREAM, 0)
创建TCP流式套接字。 - 绑定地址:使用
bind()
函数绑定服务器的IP地址和端口号。 - 监听连接:使用
listen()
函数开始监听客户端的连接请求。 - 接受连接:使用
accept()
函数接受客户端连接请求,返回一个新的套接字用于与特定客户端通信。 - 收发数据:使用
recv()
和send()
函数进行数据接收和发送,也可以使用标准I/O函数read()
和write()
。 - 关闭套接字:使用
close()
函数关闭新创建的通信套接字和监听套接字。
Client端流程:
- 创建套接字:使用
socket(AF_INET, SOCK_STREAM, 0)
创建TCP流式套接字。 - 连接服务器:使用
connect()
函数连接到服务器的指定地址和端口。 - 收发数据:使用
send()
和recv()
函数与服务器进行数据通信,也可以使用标准I/O函数read()
和write()
。 - 关闭套接字:使用
close()
函数关闭套接字。
函数
listen
int listen(int sockfd, int backlog);
作用
listen
函数将由 sockfd
指定的套接字标记为被动套接字,即用于接受使用 accept(2)
到达的连接请求的套接字。
参数
int sockfd
:引用一个类型为SOCK_STREAM
或SOCK_SEQPACKET
的套接字的文件描述符。int backlog
:定义sockfd
的待处理连接队列的最大长度。如果连接请求到达时队列已满,客户端可能会收到一个带有ECONNREFUSED
指示的错误,或者如果底层协议支持重传,请求可能会被忽略,以便稍后重新尝试连接时成功。
返回值
- 成功时返回零。
- 失败时返回 -1,并设置
errno
以指示错误类型。
accept 和 accept4
int accept(int sockfd, struct sockaddr *_Nullable restrict addr, socklen_t *_Nullable restrict addrlen);
int accept4(int sockfd, struct sockaddr *_Nullable restrict addr, socklen_t *_Nullable restrict addrlen, int flags);
作用
accept()
和 accept4()
系统调用用于面向连接的套接字类型(如 SOCK_STREAM
和 SOCK_SEQPACKET
)。它们从监听套接字 sockfd
的待处理连接队列中提取第一个连接请求,创建一个新的已连接套接字,并返回一个指向该套接字的新文件描述符。
新创建的套接字处于非监听状态,原始套接字 sockfd
不受此调用影响。
参数
int sockfd
:一个已经通过socket(2)
创建、通过bind(2)
绑定到本地地址,并在listen(2)
后监听连接的套接字。struct sockaddr *addr
:指向sockaddr
结构的指针。此结构将被填入对端套接字地址。如果addr
为 NULL,则不填充任何内容;在这种情况下,addrlen
也不使用,应该也为 NULL。socklen_t *addrlen
:这是一个值-结果参数:调用者必须初始化它以包含由addr
指向的结构的大小;返回时,它将包含对端地址的实际大小。int flags
:用于accept4()
的标志位,如果flags
为 0,则accept4()
行为与accept()
相同。flags
参数按位或来获得不同行为:
选项 | 说明 |
---|---|
SOCK_NONBLOCK | 在新的文件描述符所引用的打开文件描述上设置 O_NONBLOCK 文件状态标志。使用此标志可以节省额外调用 fcntl(2) 来实现相同结果的操作。 |
SOCK_CLOEXEC | 在新的文件描述符上设置 close-on-exec (FD_CLOEXEC ) 标志。请参阅 open(2) 中对 O_CLOEXEC 标志的描述以了解为何这可能有用。 |
返回值
- 成功时,这些系统调用返回接受套接字的文件描述符(非负整数)。
- 错误时返回 -1,并设置
errno
指示错误类型,addrlen
保持不变。
connect
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
作用
connect
系统调用用于将由文件描述符 sockfd
指定的套接字连接到 addr
指定的地址。addrlen
参数指定 addr
的大小。
如果套接字 sockfd
的类型是 SOCK_DGRAM
,则 addr
是默认发送数据报的地址,也是接收数据报的唯一地址。如果套接字的类型是 SOCK_STREAM
或 SOCK_SEQPACKET
,则此调用尝试连接到绑定到 addr
指定地址的套接字。
参数
int sockfd
:指定套接字的文件描述符。const struct sockaddr *addr
:指向套接字地址结构的指针。socklen_t addrlen
:指定地址结构的大小。
返回值
- 如果连接或绑定成功,则返回零。
- 出错时返回 -1,并设置
errno
指示错误类型。
示例
server.c
#include "netinet/in.h"#include "netinet/ip.h"#include "proto.h"#include "stdio.h"#include "stdlib.h"#include "string.h"#include "sys/socket.h"#include <arpa/inet.h>#define BUFFSIZE 1024#define IPSIZE 24#define SERVER_PORT "2233"
int main() { int sd; sd = socket(AF_INET, SOCK_STREAM, 0);
int val = 1; if (setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val))) { perror("setsockop()"); exit(1); }
if (sd < 0) { perror("socket"); exit(1); } struct sockaddr_in s_addr; struct sockaddr_in c_addr; memset(&s_addr, 0, sizeof(s_addr)); s_addr.sin_family = AF_INET; s_addr.sin_port = htons(atoi(SERVER_PORT)); inet_pton(sd, "0.0.0.0", &s_addr.sin_addr);
socklen_t socklen = sizeof(s_addr); socklen_t c_socklen = sizeof(c_addr);
if (bind(sd, (void *)&s_addr, socklen) < 0) { perror("bind()"); exit(1); } if (listen(sd, 100) < 0) { perror("listen"); exit(1); } int new_sd;
new_sd = accept(sd, &c_addr, &c_socklen); if (new_sd < 0) { perror("accept()"); exit(1); } FILE *sock_fl; sock_fl = fdopen(new_sd, "w"); if (sock_fl == NULL) { perror("fdopen()"); exit(1); } char addr[22]; inet_ntop(AF_INET, &c_addr.sin_addr, addr, c_socklen); int port = ntohs(c_addr.sin_port); fprintf(sock_fl, "Hello %s:%d \n", addr, port); fclose(sock_fl); return 0;}
使用nc命令测试程序
➜ ~ nc 127.0.0.1 2233Hello 127.0.0.1
client.c
#include "arpa/inet.h"#include "netinet/ip.h"#include "proto.h"#include "stdio.h"#include "stdlib.h"#include "string.h"#include "sys/socket.h"
#define BUFF_SIZE 2048#define SERVER_PORT "2233"
int main(int argc, char *argv[]) { int sd; sd = socket(AF_INET, SOCK_STREAM, 0); if (sd < 0) { perror("socket()"); exit(1); } struct sockaddr_in s_addr; s_addr.sin_family = AF_INET; inet_pton(AF_INET, "0.0.0.0", &s_addr.sin_addr); s_addr.sin_port = htons(atoi(SERVER_PORT));
socklen_t socklen = sizeof(s_addr);
if (connect(sd, &s_addr, socklen) < 0) { perror("connect()"); exit(1); }
FILE *sock_fl; sock_fl = fdopen(sd, "r"); if (sock_fl == NULL) { perror("sock_fl()"); exit(1); } char buf[BUFF_SIZE]; int cnt;
while ((cnt = fread(buf, 1, BUFF_SIZE, sock_fl)) > 0) { // 使用 fwrite 将读取到的 cnt 字节直接写入标准输出 fwrite(buf, 1, cnt, stdout); }
fclose(sock_fl); return 0;}
使用nc命令测试程序,client会打印file中的内容。
nc -l -p 2233 < file
补充
命令nc
Ncat 是一个功能丰富的网络实用程序,可以从命令行跨网络读取和写入数据。
nc [选项] [目标主机] [端口]
主要功能:
客户端
nc 192.168.1.1 1234
端口扫描
nc -zv 192.168.1.1 80-100
监听端口
nc -l -p 1234
服务端传送文件
nc -l -p 1234 < file.txt
网络套接字:流式套接字&TCP【Unix编程】
https://milkfunc.top/posts/unix环境高级编程学习摘要/网络套接字/网络套接字流式套接字/