socket 编程
服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),while-true里调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。
server
以下是一个简单的使用C语言实现的Socket服务端代码示例,该服务端将持续监听特定端口,并接收来自客户端的连接:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <stdbool.h> #include <arpa/inet.h>
#define PORT 8080 #define BUFFER_SIZE 1024
void error(int server_fd){ perror("listen failed"); close(server_fd); exit(EXIT_FAILURE); }
int main() { int server_fd, new_socket; struct sockaddr_in address; int addrlen = sizeof(address); char buffer[BUFFER_SIZE] = {0};
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { perror("socket failed"); exit(EXIT_FAILURE); }
address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) { error(server_fd); }
if (listen(server_fd, 3) < 0) { error(server_fd); }
while(true){ printf("Waiting for a connection...\n");
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) { error(server_fd); }
read(new_socket, buffer, BUFFER_SIZE); printf("Message received: %s\n", buffer);
char* response = "Nihao, Client!"; send(new_socket, response, strlen(response)<=BUFFER_SIZE?strlen(response):BUFFER_SIZE, 0); printf("Message echoed back to client\n");
close(new_socket); } close(server_fd); return 0; }
|
- 创建Socket:使用
socket()
函数创建一个套接字,AF_INET
表示使用IPv4协议,SOCK_STREAM
表示使用TCP协议。
- 绑定Socket:通过
bind()
函数将套接字绑定到特定的IP地址和端口号。
- 监听端口:使用
listen()
函数使套接字进入监听状态,等待客户端连接。
- 接受连接:
accept()
函数会阻塞程序,直到有客户端连接。成功连接后会返回一个新的套接字用于与客户端通信。
- 读取数据:通过
read()
函数读取客户端发送的数据,并在服务器端打印出来。
- 发送响应:使用
send()
函数将响应消息发送回客户端。
- 关闭连接:通过
close()
函数关闭与客户端的连接。
这个服务端程序会持续运行并处理来自多个客户端的连接。
client
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <arpa/inet.h>
#define PORT 8080 #define BUFFER_SIZE 1024
int main() { int sock = 0; struct sockaddr_in serv_addr; char *message = "Hello, Server!"; char buffer[BUFFER_SIZE] = {0};
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { printf("Socket creation error\n"); return -1; }
serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(PORT);
if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) { printf("Invalid address/ Address not supported\n"); return -1; }
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { printf("Connection Failed\n"); return -1; } send(sock, message, strlen(message), 0); printf("Message sent: %s\n", message);
read(sock, buffer, BUFFER_SIZE); printf("Message received from server: %s\n", buffer); close(sock); return 0; }
|
- 创建Socket:使用
socket()
函数创建一个套接字,AF_INET
表示使用IPv4协议,SOCK_STREAM
表示使用TCP协议。
- 设置服务器地址:指定服务器的IP地址和端口号,
inet_pton()
函数将IPv4地址从文本形式转换为二进制形式。
- 连接服务器:通过
connect()
函数尝试与服务器建立连接。
- 发送消息:使用
send()
函数将消息发送给服务器。
- 读取响应:通过
read()
函数读取服务器返回的数据,并打印出来。
- 关闭套接字:通过
close()
函数关闭与服务器的连接。
运行这个客户端程序时,它会连接到在本地IP地址127.0.0.1
、端口8080
上运行的服务器,发送一个消息,然后接收并打印来自服务器的响应。
socket 函数
在上述C语言Socket编程示例中,涉及了几个关键的Socket相关函数和参数。以下是这些函数和参数的详细解释:
1. socket()
1
| int socket(int domain, int type, int protocol);
|
- 功能: 创建一个新的套接字(Socket),并返回一个文件描述符,用于后续的网络通信。
- 参数:
domain
: 协议族。常用的选项包括:
AF_INET
: IPv4协议。
AF_INET6
: IPv6协议。
type
: 套接字类型。常用的选项包括:
SOCK_STREAM
: 面向连接的TCP协议。
SOCK_DGRAM
: 无连接的UDP协议。
protocol
: 通常为0,表示使用默认协议。对于SOCK_STREAM
,默认为TCP;对于SOCK_DGRAM
,默认为UDP。
2. bind()
1
| int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
|
- 功能: 将套接字绑定到一个指定的IP地址和端口号上。
- 参数:
sockfd
: socket()
返回的文件描述符。
addr
: 一个指向struct sockaddr
类型的指针,包含IP地址和端口号信息。
addrlen
: addr
结构体的大小(字节数)。
3. listen()
1
| int listen(int sockfd, int backlog);
|
- 功能: 使服务器套接字进入被动监听状态,等待客户端连接。
- 参数:
sockfd
: socket()
返回的文件描述符。
backlog
: 等待连接队列的最大长度。
4. accept()
1
| int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
|
- 功能: 从已连接队列中提取第一个连接请求,创建一个新套接字用于与客户端通信。
- 参数:
sockfd
: socket()
返回的文件描述符(监听套接字)。
addr
: 一个指向struct sockaddr
类型的指针,用于存储客户端的IP地址和端口号。
addrlen
: addr
结构体的大小(字节数),传入时需要初始化,返回时会被更新为实际大小。
5. connect()
1
| int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
|
- 功能: 客户端使用此函数连接到指定的服务器地址和端口。
- 参数:
sockfd
: socket()
返回的文件描述符。
addr
: 一个指向struct sockaddr
类型的指针,包含服务器的IP地址和端口号信息。
addrlen
: addr
结构体的大小(字节数)。
6. send()
1
| ssize_t send(int sockfd, const void *buf, size_t len, int flags);
|
- 功能: 通过套接字发送数据。
- 参数:
sockfd
: 套接字文件描述符。
buf
: 指向要发送的数据的指针。
len
: 要发送的数据的长度。
flags
: 通常设为0。可以设置为其他标志位,如MSG_DONTWAIT
(非阻塞发送)等。
7. recv()
1
| ssize_t recv(int sockfd, void *buf, size_t len, int flags);
|
- 功能: 通过套接字接收数据。
- 参数:
sockfd
: 套接字文件描述符。
buf
: 指向接收缓冲区的指针。
len
: 接收缓冲区的大小。
flags
: 通常设为0。可以设置为其他标志位,如MSG_DONTWAIT
(非阻塞接收)等。
8. close()
9. inet_pton()
1
| int inet_pton(int af, const char *src, void *dst);
|
- 功能: 将IP地址从文本格式转换为二进制格式,存储在
struct sockaddr_in
或struct sockaddr_in6
中。
- 参数:
af
: 地址族(例如,AF_INET
表示IPv4)。
src
: 以字符串形式表示的IP地址(例如 "127.0.0.1"
)。
dst
: 用于存储转换后的二进制IP地址的缓冲区(通常为struct in_addr
或struct in6_addr
的成员)。
10. htons()
1
| uint16_t htons(uint16_t hostshort);
|
- 功能: 将主机字节序转换为网络字节序。常用于将端口号从主机字节序转换为网络字节序,以确保在网络传输中的正确性。
- 参数:
结构体 struct sockaddr_in
1 2 3 4 5 6
| struct sockaddr_in { short int sin_family; unsigned short int sin_port; struct in_addr sin_addr; unsigned char sin_zero[8]; };
|
- 解释:
sin_family
: 协议族,通常为AF_INET
表示IPv4。
sin_port
: 端口号,使用htons()
将端口号转换为网络字节序。
sin_addr
: struct in_addr
结构体,表示IPv4地址。
sin_zero
: 填充字段,用于保证struct sockaddr_in
与struct sockaddr
大小一致。