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.1client.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环境高级编程学习摘要/网络套接字/网络套接字流式套接字/
鲁公网安备37011302000501号