Socket 是网络编程的一个抽象概念,它是对
TCP/IP
协议的封装,提供了一组接口,使得程序员可以更方便地使用网络功能。
# Socket 的原理
Socket
的原理是基于 TCP/IP
协议的,它通过 TCP/IP
协议来传输数据。 TCP/IP
协议是一种网络通信协议,它定义了计算机之间如何进行通信。
# TCP
TCP
(Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。它提供了一种可靠的、有序的、无重复的数据传输方式。 TCP
协议通过三次握手建立连接,通过四次挥手断开连接。在数据传输过程中, TCP
协议会进行流量控制和拥塞控制,以保证数据的可靠传输。
三次握手:
- 客户端向服务器发送一个
SYN
包,表示请求建立连接。 - 服务器收到
SYN
包后,向客户端发送一个SYN-ACK
包,表示同意建立连接。 - 客户端收到
SYN-ACK
包后,向服务器发送一个 ACK 包,表示确认建立连接。
四次挥手:
- 客户端向服务器发送一个
FIN
包,表示请求断开连接。 - 服务器收到 FIN 包后,向客户端发送一个
ACK
包,表示确认断开连接。 - 服务器向客户端发送一个 FIN 包,表示请求断开连接。
- 客户端收到
FIN
包后,向服务器发送一个ACK
包,表示确认断开连接。
# Socket 的使用
# 服务器端通信流程
- 创建用于监听的套接字,这个套接字是一个文件描述符:
int lfd = socket( AF_INET, SOCK_STREAM, 0 ); |
socket
接受三个参数,分别是 地址族
、 套接字类型
和 协议类型
。
AF_INET
表示使用 IPv4
协议, SOCK_STREAM
表示使用 TCP
协议, 0
表示使用默认协议。
- 绑定套接字到指定的 IP 地址和端口号:
struct sockaddr_in serv_addr; | |
serv_addr.sin_family = AF_INET; | |
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 使用本地 IP 地址 | |
serv_addr.sin_port = htons(8888); // 使用指定的端口号 | |
bind( lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr) ); |
bind
接受三个参数,分别是 套接字描述符
、 地址结构体
和 地址结构体
的大小。
- 监听套接字:
listen( lfd, 128 ); |
listen 接受两个参数,分别是 套接字描述符
和 监听队列的长度
。
- 接受客户端连接:
struct sockaddr_in cli_addr; | |
socklen_t cli_len = sizeof(cli_addr); | |
int cfd = accept( lfd, (struct sockaddr*)&cli_addr, &cli_len ); |
accept
接受三个参数,分别是 套接字描述符
、 客户端地址结构体
和 客户端地址结构体
的大小。
- 读取客户端发送的数据:
char buf[1024]; | |
int n = read( cfd, buf, sizeof(buf) ); |
read
接受三个参数,分别是 套接字描述符
、 缓冲区
和 缓冲区大小
。
- 发送数据给客户端:
write( cfd, "hello world", 11 ); |
write
接受三个参数,分别是 套接字描述符
、 数据
和 数据大小
。
- 关闭套接字:
close( lfd );// 关闭监听套接字 | |
close( cfd );// 关闭客户端套接字 |
close
接受一个参数,即 套接字描述符
。
服务端程序的完整代码如下:
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <unistd.h> | |
#include <arpa/inet.h> | |
int main() | |
{ | |
// 1. 创建用于监听的套接字 | |
int lfd = socket(AF_INET, SOCK_STREAM, 0); | |
if (lfd == -1) | |
{ | |
perror("socket"); | |
exit(1); | |
} | |
// 2. 绑定套接字到指定的 IP 地址和端口号 | |
struct sockaddr_in serv_addr; | |
serv_addr.sin_family = AF_INET; | |
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); | |
serv_addr.sin_port = htons(8888); | |
if (bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) | |
{ | |
perror("bind"); | |
exit(1); | |
} | |
// 3. 监听套接字 | |
if (listen(lfd, 128) == -1) | |
{ | |
perror("listen"); | |
exit(1); | |
} | |
// 4. 接受客户端连接 | |
struct sockaddr_in cli_addr; | |
socklen_t cli_len = sizeof(cli_addr); | |
int cfd = accept(lfd, (struct sockaddr*)&cli_addr, &cli_len); | |
if (cfd == -1) | |
{ | |
perror("accept"); | |
exit(1); | |
} | |
// 5. 读取客户端发送的数据 | |
char buf[1024]; | |
int n = read(cfd, buf, sizeof(buf)); | |
if (n == -1) | |
{ | |
perror("read"); | |
exit(1); | |
} | |
printf("recv buf: %s\n", buf); | |
// 6. 发送数据给客户端 | |
if (write(cfd, "hello world", 11) == -1) | |
{ | |
perror("write"); | |
exit(1); | |
} | |
// 7. 关闭套接字 | |
close(lfd); | |
close(cfd); | |
return 0; | |
} |
# 客户端通信流程
- 创建用于连接的套接字:
int cfd = socket( AF_INET, SOCK_STREAM, 0 ); |
- 连接服务器:
struct sockaddr_in serv_addr; | |
serv_addr.sin_family = AF_INET; | |
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 使用服务器的 IP 地址 | |
serv_addr.sin_port = htons(8888); // 使用指定的端口号 | |
connect( cfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr) ); |
- 发送数据给服务器:
write( cfd, "hello world", 11 ); |
- 读取服务器发送的数据:
char buf[1024]; | |
int n = read( cfd, buf, sizeof(buf) ); |
- 关闭套接字:
close( cfd ); |
客户端程序的完整代码如下:
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <unistd.h> | |
#include <arpa/inet.h> | |
int main() | |
{ | |
// 1. 创建用于连接的套接字 | |
int cfd = socket(AF_INET, SOCK_STREAM, 0); | |
if (cfd == -1) | |
{ | |
perror("socket"); | |
exit(1); | |
} | |
// 2. 连接服务器 | |
struct sockaddr_in serv_addr; | |
serv_addr.sin_family = AF_INET; | |
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); | |
serv_addr.sin_port = htons(8888); | |
if (connect(cfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) | |
{ | |
perror("connect"); | |
exit(1); | |
} | |
// 3. 发送数据给服务器 | |
if (write(cfd, "hello world", 11) == -1) | |
{ | |
perror("write"); | |
exit(1); | |
} | |
// 4. 读取服务器发送的数据 | |
char buf[1024]; | |
int n = read(cfd, buf, sizeof(buf)); | |
if (n == -1) | |
{ | |
perror("read"); | |
exit(1); | |
} | |
printf("recv buf: %s\n", buf); | |
// 5. 关闭套接字 | |
close(cfd); | |
return 0; | |
} |