本文面向初学者,目标是让你“一看就懂、能马上动手实践”。从零讲起,手把手梳理服务端与客户端的构建全过程,每个函数、参数、典型用法、数据流向、底层机制、注意事项全部细细拆解,让你彻底明白如何让两个程序通过网络可靠通信。
什么是Socket?
IP和端口
TCP通信流程(面向连接)
服务端主要流程:
客户端主要流程:
作用:告诉内核“我要用网络通信”,创建一个通信端点。
#include
#include
int socket(int domain, int type, int protocol);
AF_INET
(IPv4)。SOCK_STREAM
(TCP,可靠流)。举例:
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("socket error");
exit(1);
}
作用:明确告诉操作系统“我用哪个IP+端口”来等待客户端连接。只有bind了,别人才能找到你。
#include
#include
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET; // 协议族
servaddr.sin_port = htons(8888); // 端口(本地字节序转网络字节序)
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 本机所有IP
int ret = bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
if (ret == -1) {
perror("bind error");
exit(1);
}
htons
/htonl
是将主机字节序转为网络字节序(大端)。INADDR_ANY
让你的服务监听本机所有网卡(IP)。作用:让socket进入“监听”状态,准备接收连接请求。
int listen(int sockfd, int backlog);
举例:
if (listen(sockfd, 128) == -1) {
perror("listen error");
exit(1);
}
作用:阻塞等待客户端“来电”,接听一个新连接,为每个连接分配一个新的socket。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
举例:
struct sockaddr_in cliaddr;
socklen_t cliaddr_len = sizeof(cliaddr);
int connfd = accept(sockfd, (struct sockaddr*)&cliaddr, &cliaddr_len);
if (connfd == -1) {
perror("accept error");
continue; // 或exit(1)
}
作用:主动“打电话”给服务端,发起三次握手,连接指定IP+端口。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
客户端配置服务器地址:
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(8888);
inet_pton(AF_INET, "192.168.1.100", &servaddr.sin_addr);
if (connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1) {
perror("connect error");
exit(1);
}
作用:收发字节流数据,就像读写文件一样。
write()
发送数据到对方read()
从对方接收数据例子(双方都类似):
char buf[1024];
// 发送
write(sockfd, "hello", 5);
// 接收
int n = read(sockfd, buf, sizeof(buf)-1);
if (n > 0) {
buf[n] = '\0';
printf("收到: %s\n", buf);
}
作用:释放系统资源,断开连接。
close(sockfd); // 对服务端监听socket、通信socket、客户端socket都适用
服务器端流程 客户端流程
--------------------------------------------------------
socket() socket()
| |
bind() connect()
| |
listen() |
| ---------三次握手
accept() <--------+ |
| | |
read()/write() <---+---> read()/write()
| | |
close() close() close()
例子:
if (bind(sockfd, ...) == -1) {
perror("bind error");
// fprintf(logfile, "bind error: %s\n", strerror(errno));
exit(1);
}
#include
#include
#include
#include
#include
#define SERVER_PORT 8888
int main() {
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERVER_PORT);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1) {
perror("bind error");
exit(1);
}
if (listen(listenfd, 128) == -1) {
perror("listen error");
exit(1);
}
printf("Server is listening...\n");
while (1) {
struct sockaddr_in cliaddr;
socklen_t cliaddr_len = sizeof(cliaddr);
int connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &cliaddr_len);
if (connfd == -1) {
perror("accept error");
continue;
}
char buf[1024];
int n = read(connfd, buf, sizeof(buf)-1);
if (n > 0) {
buf[n] = '\0';
printf("client says: %s\n", buf);
write(connfd, buf, n); // 回显
}
close(connfd);
}
close(listenfd);
return 0;
}
#include
#include
#include
#include
#include
#define SERVER_PORT 8888
#define SERVER_IP "127.0.0.1"
int main() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERVER_PORT);
inet_pton(AF_INET, SERVER_IP, &servaddr.sin_addr);
if (connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1) {
perror("connect error");
exit(1);
}
char sendbuf[1024] = "hello server";
write(sockfd, sendbuf, strlen(sendbuf));
char recvbuf[1024];
int n = read(sockfd, recvbuf, sizeof(recvbuf)-1);
if (n > 0) {
recvbuf[n] = '\0';
printf("server says: %s\n", recvbuf);
}
close(sockfd);
return 0;
}
netstat -ntlp
查端口占用,或改端口再试。| 客户端 | 网络 | 服务端 |
+--------+---------------+-----------------------+
| | ---connect--> | [listenfd] |
| | <---三次握手--| |
| | | accept()产生[connfd] |
| |<->read/write<>| [connfd]<->[listenfd] |
| | ---close----->| [connfd closed] |
只要理解并掌握上面每一步、每个关键函数的使用,你就能独立搭建出稳定可靠的C语言网络通信程序!每次遇到问题都可以翻回来看,一步步排查流程,问题迎刃而解。