目录
TCP编程流程
套接字函数
1.创建套接字
2.绑定地址
3.监听连接请求
4.接受连接
5. 连接到服务器
6. 发送数据
7. 接收数据
8.关闭套接字
服务器端通信流程
示例代码
客户端通信流程
代码示例
TCP编程流程
TCP是一个面向连接的,安全的,流式传输协议,这个协议是一个传输层协议。
- 面向连接:是一个双向连接,通过三次握手完成,断开连接需要通过四次挥手完成。
- 安全:tcp通信过程中,会对发送的每一数据包都会进行校验, 如果发现数据丢失, 会自动重传
- 流式传输:发送端和接收端处理数据的速度,数据的量都可以不一致。
TCP 的服务器端和客户端编程流程如下:
套接字函数
套接字编程中,常用的套接字函数通常涵盖套接字的创建、绑定、监听、连接、发送、接收、关闭等操作。以下是一些常用的套接字函数,这些函数通常在C语言的
<sys/socket.h>
头文件中声明:1.创建套接字
int socket(int domain, int type, int protocol);
参数:
domain
:使用的地址协议族,如AF_INET、AF_INET6分别
表示IPv4、IPv6格式。type
:套接字类型,如 SOCK_STREAM(流式传输协议)
表示TCP套接字,SOCK_DGRAM(报式传输协议)表示UDP套接字。protocol
:通常为0,表示自动选择协议。返回值:
- 成功:返回新创建套接字的文件描述符。
- 失败:返回-1,并设置
errno
。2.绑定地址
// 将文件描述符和本地的IP与端口进行绑定 int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
sockfd
:套接字的文件描述符,通过socket调用得到的返回值。addr
:包含要绑定的IP地址和端口号的结构体。addrlen
:addr
结构体的大小,sizeof(addr)。返回值:
- 成功:0,失败:返回-1。
3.监听连接请求
// 给监听的套接字设置监听 int listen(int sockfd, int backlog);
参数:
sockfd
:套接字的文件描述符,通过socket调用得到的返回值。backlog
:在进入队列中等待接受的最大连接数,最大值为128。返回值:
- 成功:0,失败:返回-1。
4.接受连接
// 等待并接受客户端的连接请求, 建立新的连接, 会得到一个新的文件描述符(通信的) int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数:
sockfd
:套接字的文件描述符。addr
:用于存储客户端地址信息的结构体。addrlen
:addr
结构体的大小。返回值:
- 函数调用成功,得到一个文件描述符, 用于和建立连接的这个客户端通信,调用失败返回 -1。
这个函数是一个阻塞函数,当没有新的客户端连接请求的时候,该函数阻塞;当检测到有新的客户端连接请求时,阻塞解除,新连接就建立了,得到的返回值也是一个文件描述符,基于这个文件描述符就可以和客户端通信了。
5. 连接到服务器
// 成功连接服务器之后, 客户端会自动随机绑定一个端口 // 服务器端调用accept()的函数, 第二个参数存储的就是客户端的IP和端口信息 int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
sockfd
:套接字的文件描述符,通过socket调用得到的返回值。addr
:用于存储客户端地址信息的结构体,这个IP和端口也需要转换为大端然后再赋值。addrlen
:addr
结构体的大小。返回值:
- 连接成功返回0,连接失败返回-1
6. 发送数据
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
参数:
sockfd
:套接字的文件描述符。buf
:包含要发送数据的缓冲区。len
:要发送的数据的长度。flags
:发送标志,通常为0。返回值:
- 成功:返回发送的字节数。
- 失败:返回-1。
7. 接收数据
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数:
sockfd
:套接字的文件描述符。buf
:用于接收数据的缓冲区。len
:要接收的数据的长度。flags
:接收标志,通常为0。返回值:
- 成功:返回接收的字节数。
- 失败:返回-1。
8.关闭套接字
int close(int sockfd);
参数:
sockfd
:套接字的文件描述符。返回值:
- 成功:0,失败:返回-1。
服务器端通信流程
1.创建套接字: 使用
socket
函数创建一个套接字,指定协议族(通常是AF_INET
表示IPv4)、套接字类型(SOCK_STREAM
表示TCP流套接字)、协议(通常为0,表示自动选择协议)。int server_socket = socket(AF_INET, SOCK_STREAM, 0);
2.绑定地址: 使用
bind
函数将套接字与特定的IP地址和端口号绑定。struct sockaddr_in server_address; server_address.sin_family = AF_INET; server_address.sin_port = htons(8888); server_address.sin_addr.s_addr = INADDR_ANY; bind(server_socket, (struct sockaddr *)&server_address, sizeof(server_address));
3.监听连接请求: 使用
listen
函数开始监听连接请求。listen(server_socket, 5); // 允许最多5个连接请求排队
4.接受连接: 使用
accept
函数接受客户端的连接请求,该函数会阻塞程序直到有客户端连接进来。int client_socket = accept(server_socket, NULL, NULL);
5.进行数据交互: 使用
send
和recv
函数进行数据的发送和接收。char buffer[1024]; recv(client_socket, buffer, sizeof(buffer), 0); send(client_socket, "Hello from server", strlen("Hello from server"), 0);
6.关闭套接字: 使用
close
函数关闭服务端的套接字。close(server_socket);
示例代码
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> int main() { // 1.创建监听的套接字 int fd = socket(AF_INET, SOCK_STREAM, 0); if (fd == -1) { perror("socket"); return -1; } // 2.绑定本地的IP port struct sockaddr_in saddr; saddr.sin_family = AF_INET; saddr.sin_port = htons(9999); saddr.sin_addr.s_addr = INADDR_ANY; // INADD_ANY自动读取本地的ip地址 int ret = bind(fd, (struct sockaddr *)&saddr, sizeof(saddr)); if (ret == -1) { perror("bind"); return -1; } // 3.设置监听 ret = listen(fd, 128); if (ret == -1) { perror("listen"); return -1; } // 4.阻塞并等待客户端的连接 struct sockaddr_in caddr; int addrlen = sizeof(caddr); int cfd = accept(fd, (struct sockaddr *)&caddr, &addrlen); if (cfd == -1) { perror("accept"); return -1; } // 连接建立成功,打印客户端的ip和端口信息 char ip[32]; printf("客户端的ip:%s,端口:%d\n", inet_ntop(AF_INET, &caddr.sin_addr.s_addr, ip, sizeof(ip)), ntohs(caddr.sin_port)); // 5.通信 while (1) { // 接受数据 char buff[1024]; int len = recv(cfd, buff, sizeof(buff), 0); if (len > 0) { printf("client say:%s\n", buff); send(cfd, buff, len, 0); } else if (len == 0) { printf("客户端已经断开了连接...\n"); break; } else { perror("recv"); break; } } close(fd); return 0; }
客户端通信流程
1.创建套接字: 使用
socket
函数创建一个套接字。int client_socket = socket(AF_INET, SOCK_STREAM, 0);
2.设置服务器地址: 设置服务器的地址信息,包括协议族、IP地址和端口号。
struct sockaddr_in server_address; server_address.sin_family = AF_INET; server_address.sin_port = htons(8888); server_address.sin_addr.s_addr = inet_addr("127.0.0.1");
3.连接服务器: 使用
connect
函数连接到服务器。connect(client_socket, (struct sockaddr *)&server_address, sizeof(server_address));
4.进行数据交互: 使用
send
和recv
函数进行数据的发送和接收。char buffer[1024]; send(client_socket, "Hello from client", strlen("Hello from client"), 0); recv(client_socket, buffer, sizeof(buffer), 0);
5.关闭套接字: 使用
close
函数关闭客户端的套接字。close(client_socket);
代码示例
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> int main() { // 1.创建通信的套接字 int fd = socket(AF_INET, SOCK_STREAM, 0); if (fd == -1) { perror("socket"); return -1; } // 2.连接服务器的IP port struct sockaddr_in saddr; saddr.sin_family = AF_INET; saddr.sin_port = htons(9999); inet_pton(AF_INET, "192.168.3.128", &saddr.sin_addr.s_addr); int ret = connect(fd, (struct sockaddr *)&saddr, sizeof(saddr)); if (ret == -1) { perror("connect"); return -1; } int number = 0; // 3.通信 while (1) { // 发送数据 char buff[1024]; sprintf(buff, "你好,hello,world,%d...\n", number++); send(fd, buff, sizeof(buff), 0); // 接收数据 memset(buff, 0, sizeof(buff)); int len = recv(fd, buff, sizeof(buff), 0); if (len > 0) { printf("server say:%s\n", buff); } else if (len == 0) { printf("服务器端已经断开了连接...\n"); break; } else { perror("recv"); break; } sleep(1); } // 关闭文件描述符 close(fd); return 0; }