在C语言中进行网络编程,核心是围绕着套接字(Socket)展开,套接字是一种抽象,它允许程序通过网络进行通信,就好像在本地机器上的不同进程间通信一样,主要有流套接字(面向连接,提供可靠的数据传输服务,使用TCP协议)和数据报套接字(无连接,不保证可靠传输,使用UDP协议)这两种类型。
当你想开发一个简单的客户端-服务器聊天应用程序时,如果选择流套接字,服务器端会先创建一个套接字,然后绑定到本机的某个端口上开始监听客户端的连接请求,客户端同样创建一个套接字,主动向服务器发起连接,连接成功后双方就可以通过这个套接字进行数据的收发了,就像通过一根管道传递信息,而这个管道就是由套接字机制建立起来的。
1、socket函数
功能:用于创建一个套接字,需要指定套接字的类型(如SOCK_STREAM表示流套接字,SOCK_DGRAM表示数据报套接字)、协议类型(通常为0,表示默认协议,对于TCP是IPPROTO_TCP,对于UDP是IPPROTO_UDP)。
示例代码:int sockfd = socket(AF_INET, SOCK_STREAM, 0);
这里AF_INET表示使用IPv4地址族。
2、bind函数
功能:将创建好的套接字与本机的一个IP地址和端口号绑定在一起,这样服务器就能确定从哪个网络接口接收数据了。
示例代码:struct sockaddr_in serv_addr; serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(8080); serv_addr.sin_addr.s_addr = INADDR_ANY; bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
这段代码将套接字绑定到了本机的8080端口,并且可以接受来自任意IP地址的连接(因为使用了INADDR_ANY)。
3、listen函数
功能:仅用于流套接字,使套接字进入被动的监听状态,等待客户端的连接请求,需要指定监听的套接字描述符以及能够同时处理的最大连接请求数(通常称为监听套接字数)。
示例代码:listen(sockfd, 5);
表示允许最多有5个客户端连接请求处于等待队列中。
4、accept函数
功能:也是用于流套接字,从已完成连接队列头部取出一个连接,为其创建一个新的套接字描述符来与之通信,如果没有可用连接,进程会被阻塞在这里,直到有新的连接建立。
示例代码:int new_sock = accept(sockfd, (struct sockaddr *)&cli_addr, &cli_len);
当有新的客户端连接成功时,会返回一个新的套接字描述符new_sock,通过这个新套接字就可以与该客户端进行数据交互了。
5、send和recv函数
功能:send用于发送数据,recv用于接收数据,它们都需要指定套接字描述符、要发送/接收的数据缓冲区以及数据的长度等参数。
示例代码:send(new_sock, "Hello, client!", 13, 0);
这行代码会通过套接字new_sock向客户端发送字符串“Hello, client!”;char buffer[1024]; recv(new_sock, buffer, sizeof(buffer), 0);
则是从套接字new_sock接收数据到buffer数组中。
6、close函数
功能:关闭一个套接字描述符,释放与之相关的资源,无论是服务器还是客户端,在完成通信后都应该及时关闭套接字。
示例代码:close(sockfd);
关闭前面创建的套接字描述符sockfd对应的套接字。
三、简单的网络编程示例(基于TCP的回声服务器)
以下是一个用C语言实现的简单TCP回声服务器的示例代码框架:
|代码部分|内容描述|
|—|—|
|包含头文件|“`c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
“`|引入标准输入输出、字符串操作、Unix标准函数定义、网络地址转换、套接字编程等相关头文件。|
|主函数|“`c
int main() {
int serv_sock, clnt_sock;
struct sockaddr_in serv_addr, clnt_addr;
socklen_t clnt_addr_size;
char message[1024];
int str_len;
// 创建套接字
serv_sock = socket(PF_INET, SOCK_STREAM, 0);
if (serv_sock == -1) {
perror("socket creation failed");
exit(1);
}
// 初始化服务器地址结构体
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(12345);
// 绑定套接字
if (bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) {
perror("bind failed");
close(serv_sock);
exit(1);
}
// 开始监听
if (listen(serv_sock, 5) == -1) {
perror("listen failed");
close(serv_sock);
exit(1);
}
// 接受客户端连接并处理
while (1) {
clnt_addr_size = sizeof(clnt_addr);
clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
if (clnt_sock == -1) {
perror("accept failed");
continue;
} else {
printf("Client connected: %s:%d
", inet_ntoa(clnt_addr.sin_addr), ntohs(clnt_addr.sin_port));
}
// 接收客户端发送的消息并回显
str_len = recv(clnt_sock, message, sizeof(message) 1, 0);
if (str_len == -1) {
perror("recv failed");
close(clnt_sock);
continue;
}
message[str_len] = ‘