目录
1. 套接字 (Socket)
2. 基于TCP通信的流程
服务器端
客户端
1. TCP通信API
1.1 创建套接字描述符socket
1.2 绑定IP和端口号bind
1.3 设置监听状态 listen
1.4 接受连接请求 accept
1.5 发送数据 send
1.6 接收数据 recv
2. TCP服务器代码示例
代码解释:
注意点:
3. 客户端API
连接服务器
功能:
参数说明:
返回值:
示例:
代码解释:
注意事项:
1. 套接字 (Socket)
-
套接字的本质
-
socket
套接字是一个特殊的文件,在原始Linux中,它与管道、消息队列、共享内存、信号等机制类似,只能用于主机内的进程间通信。
-
-
历史发展与通信范围
- 随着TCP/IP协议族的出现,
socket
套接字能够通过网卡,与外部主机进行通信,突破了单机限制。
- 随着TCP/IP协议族的出现,
-
socket
函数的作用- 调用
socket
函数会生成一个文件描述符。 - 主机间的不同进程可以通过对该文件描述符的读写,实现通信。
- 调用
2. 基于TCP通信的流程
服务器端
-
socket
-
创建原始套接字。
-
-
bind
- 将原始套接字与主机IP绑定(该服务器的身份,服务器以字节主机的身份通信)。
- 通过
bind
,服务器的套接字被绑定到一个明确的IP地址和端口号。 - 当客户端发起通信请求时,客户端通过这个IP地址和端口号找到服务器,从而使服务器在网络中以“唯一身份”进行通信。
- 通过
- 将原始套接字与主机IP绑定(该服务器的身份,服务器以字节主机的身份通信)。
-
listen
- 将原始套接字设置为监听状态。
- 注意:监听套接字只能用于监听,要进行实际通信需要创建一个新的套接字。
-
accept
- 接收客户端的连接请求,获取客户端信息,并生成一个新的套接字描述符,用于与该客户端通信。
-
通信与关闭
- 使用新套接字发送和接收信息。
- 完成通信后关闭套接字。
客户端
-
socket
- 创建原始套接字。
-
bind
(可选)- 客户端可以选择是否绑定IP和端口号。
- 如果绑定,则客户端只能以绑定的IP和端口号进行通信。通常情况下无需绑定,除非服务器要求接收特定客户端的信息。
-
connect
- 向服务器发送连接请求,并携带客户端的身份信息(IP和端口号)。
-
通信与关闭
- 通过套接字发送和接收信息。
- 完成通信后关闭套接字。
1. TCP通信API
1.1 创建套接字描述符socket
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
- 功能:创建一个套接字描述符。
- 参数:
domain
:通信域类型
AF_UNIX
,AF_LOCAL
: 本地通信AF_INET
: IPv4通信AF_INET6
: IPv6通信type
:通信类型
SOCK_STREAM
: 使用TCP通信协议SOCK_DGRAM
: 使用UDP通信协议protocol
:通常为0,表示自动选择与domain
和type
兼容的协议。- 返回值:成功返回套接字描述符,失败返回-1。
1.2 绑定IP和端口号bind
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- 功能:将套接字与IP和端口绑定,指定通信域。
- 参数:
sockfd
:套接字文件描述符。addr
:通信域的结构体指针,通常是struct sockaddr_in
,其中包含IP地址和端口号。addrlen
:结构体的大小。- 返回值:成功返回0,失败返回-1。
1.3 设置监听状态 listen
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
- 功能:设置套接字为监听状态,等待客户端连接。
- 参数:
sockfd
:套接字描述符。backlog
:最大等待连接数,最多为128。- 返回值:成功返回0,失败返回-1。
1.4 接受连接请求 accept
#include <sys/types.h> #include <sys/socket.h> int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- 功能:从监听队列中接受客户端连接,创建新的套接字用于通信。
- 参数:
sockfd
:监听套接字描述符。addr
:客户端信息结构体(struct sockaddr_in
),接受客户端信息。addrlen
:客户端信息结构体的大小。- 返回值:成功返回新套接字描述符,失败返回-1。
1.5 发送数据 send
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
- 功能:向其他进程发送数据。
- 参数:
sockfd
:新套接字文件描述符。buf
:发送的数据缓冲区。len
:发送的数据长度。flags
:控制发送的标志(例如MSG_DONTWAIT
表示非阻塞发送)。- 返回值:成功返回发送的字节数,失败返回-1。
1.6 接收数据 recv
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
- 功能:从其他进程接收数据。
- 参数:
sockfd
:新套接字文件描述符。buf
:接收数据的缓冲区。len
:接收数据的最大长度。flags
:控制接收的标志(例如MSG_DONTWAIT
表示非阻塞接收)。- 返回值:成功返回接收的字节数,失败返回-1。
2. TCP服务器代码示例
#include <myhead.h> #define IP "192.168.60.69" #define PORT 6666 #define BACKLOG 20 int main(int argc, const char *argv[]) { // AF_INET: IPV4通信 // SOCK_STREAM:TCP通信协议 int oldfd = socket(AF_INET, SOCK_STREAM, 0); // 1. 创建套接字 if (oldfd == -1) { perror("socket"); return -1; } // 2. 绑定IP和端口号 struct sockaddr_in server = { .sin_family = AF_INET, // IPV4 .sin_port = htons(PORT), // 端口号转为网络字节序 .sin_addr.s_addr = inet_addr(IP), // IP地址 }; if (bind(oldfd, (struct sockaddr *)&server, sizeof(server)) == -1) { perror("bind"); return -1; } // 3. 监听 if (listen(oldfd, BACKLOG) == -1) { perror("listen"); return -1; } // 4. 接受客户端连接请求,创建新的描述符用于通信 struct sockaddr_in client; // 接收客户端信息的结构体 socklen_t client_len = sizeof(client); // 计算出结构体大小 int newfd; newfd = accept(oldfd, (struct sockaddr *)&client, &client_len); if (newfd == -1) { perror("accept"); return -1; } printf("%s发来连接请求\n", inet_ntoa(client.sin_addr)); // 5. 循环收发信息 char buff[1024]; while (1) { memset(buff, 0, sizeof(buff)); // int len = read(newfd, buff, sizeof(buff)); // 传统方式 int len = recv(newfd, buff, sizeof(buff), 0); // 0: 阻塞发送,MSG_DONTWAIT: 非阻塞 if (len == 0) { printf("客户端下线\n"); break; } printf("%s\n", buff); strcat(buff, "5201314"); // 加上一句话回过去 // write(newfd, buff, sizeof(buff)); // 传统方式 send(newfd, buff, sizeof(buff), 0); // 发送 } close(oldfd); close(newfd); return 0; }
代码解释:
- 创建套接字:
socket(AF_INET, SOCK_STREAM, 0)
创建一个支持 IPv4 的 TCP 套接字。- 绑定 IP 和端口:通过
bind
函数将服务器的 IP 地址和端口号绑定到创建的套接字上。- 监听:调用
listen
函数将套接字设置为监听状态,BACKLOG
指定了等待连接队列的最大长度。- 接受客户端连接:
accept
函数用于从等待队列中接收客户端的连接请求,返回一个新的套接字文件描述符用于与客户端进行通信。- 收发信息:使用
recv
接收客户端发来的信息,并通过send
发送响应。消息中添加了"5201314"
字符串后回传给客户端。- 关闭套接字:完成通信后,通过
close
关闭原始套接字和客户端套接字。
注意点:
recv
和send
用于数据传输,它们是基于 TCP 协议的,保证数据的可靠传输。inet_ntoa
函数用于将客户端的 IP 地址转换为点分十进制格式。- 使用
strcat
拼接字符串发送回客户端。
3. 客户端API
连接服务器
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:
connect
用于客户端与服务器建立连接。它对应于服务器端的accept
函数,完成了三次握手的过程。- 客户端通过调用
connect
向服务器发送连接请求,服务器通过accept
接受连接。此时,TCP连接的三次握手完成。
参数说明:
- 参数1 (
sockfd
):套接字描述符,即客户端通过socket
函数创建的套接字。- 参数2 (
addr
):服务器的地址结构体,包含目标服务器的 IP 地址和端口号。- 参数3 (
addrlen
):addr
结构体的大小,用于指定服务器地址结构体的大小。
返回值:
- 成功:返回 0。
- 失败:返回 -1,并置位错误码。
示例:
#include <sys/socket.h> #include <arpa/inet.h> #include <stdio.h> #define SERVER_IP "192.168.60.69" #define SERVER_PORT 6666 int main() { int sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建套接字 if (sockfd == -1) { perror("socket"); return -1; } struct sockaddr_in server_addr; server_addr.sin_family = AF_INET; // IPV4 server_addr.sin_port = htons(SERVER_PORT); // 服务器端口 server_addr.sin_addr.s_addr = inet_addr(SERVER_IP); // 服务器IP // 连接到服务器 if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) { perror("connect"); return -1; } printf("成功连接到服务器\n"); // 这里可以添加发送和接收数据的代码 close(sockfd); // 关闭套接字 return 0; }
代码解释:
- 创建套接字:客户端使用
socket
函数创建一个 TCP 套接字。- 填充服务器地址结构体:
struct sockaddr_in
用于定义目标服务器的 IP 地址和端口号。- 建立连接:客户端调用
connect
函数连接到指定服务器。如果连接成功,将输出"成功连接到服务器"
。- 关闭套接字:连接完成后,使用
close
关闭套接字。
注意事项:
- 连接过程中的三次握手是自动进行的,客户端不需要手动处理三次握手的细节。
- 如果连接失败(例如服务器不可达或端口未打开),
connect
将返回 -1,并设置相应的错误码。