一、Socket通信基础
1. Socket通信基本流程
服务器端流程:
创建Socket (socket())
绑定地址和端口 (bind())
监听连接 (listen())
接受连接 (accept())
数据通信 (read()/write())
关闭连接 (close())
客户端流程:
创建Socket (socket())
连接服务器 (connect())
数据通信 (read()/write())
关闭连接 (close())
2. 关键数据结构
struct sockaddr_in 用于存储IPv4地址信息:
struct sockaddr_in {
short sin_family; // 地址族,如AF_INET
unsigned short sin_port; // 端口号
struct in_addr sin_addr; // IP地址
char sin_zero[8]; // 填充字段
};
struct in_addr {
unsigned long s_addr; // 32位IPv4地址
};
二、服务器代码详解
1. 头文件引入
#include <stdio.h> // 标准输入输出
#include <stdlib.h> // 标准库函数
#include <string.h> // 字符串处理
#include <unistd.h> // POSIX系统调用
#include <sys/types.h> // 系统数据类型
#include <sys/socket.h> // Socket相关函数
#include <netinet/in.h> // Internet地址族
这些头文件提供了Socket编程所需的基本功能。
2. 错误处理函数
void error(const char *msg) {
perror(msg); // 打印错误信息
exit(1); // 异常退出
}
perror()会根据全局变量errno打印描述性错误信息。
3. 主函数结构
int main(int argc, char *argv[]) {
if(argc < 2) {
fprintf(stderr,"Port no not provided, Program terminated");
exit(1);
}
// … 其余代码
}
检查命令行参数,确保提供了端口号。
4. 变量声明
int sockfd, newsockfd, portno;
char buffer[255];
struct sockaddr_in serv_addr, cli_addr;
socklen_t clilen;
-
sockfd: 监听Socket的文件描述符
-
newsockfd: 与客户端通信的Socket文件描述符
-
serv_addr/cli_addr: 服务器/客户端地址信息
-
clilen: 客户端地址结构长度
5. 创建Socket
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0) {
error("Error opening Socket.");
}
socket()函数参数:
-
AF_INET: IPv4地址族
-
SOCK_STREAM: 面向连接的TCP Socket
-
0: 默认协议(TCP)
6. 初始化服务器地址
bzero((char *)&serv_addr, sizeof(serv_addr));
portno = atoi(argv[1]);
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(portno);
-
bzero(): 清零内存区域
-
INADDR_ANY: 监听所有网络接口
-
htons(): 将端口号转换为网络字节序(大端)
7. 绑定Socket
if(bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
error("Binding Failed.");
bind()将Socket与地址和端口绑定。
8. 监听连接
listen(sockfd,5);
listen()开始监听连接,参数5指定等待连接队列的最大长度。
9. 接受连接
newsockfd = accept(sockfd, (struct sockaddr*)&cli_addr,&clilen);
if(newsockfd < 0)
error("Error on Accept");
accept()接受客户端连接,返回新的Socket文件描述符用于通信。
10. 计算器业务逻辑
int num1, num2, ans, choice,n;
S: n = write(newsockfd,"Enter Number 1 : ",strlen("Enter Number 1"));
if (n < 0) error("Error writing to socket");
read(newsockfd, &num1, sizeof(int));
printf("Client – Number 1 is : %d \\n",num1);
// … 类似处理num2和choice
switch (choice) {
case 1:
ans = num1 + num2;
break;
case 2:
ans = num1 – num2;
break;
case 3:
ans = num1 * num2;
break;
case 4:
ans = num1 / num2;
break;
case 5:
goto Q;
break;
}
write(newsockfd,&ans,sizeof(int));
if(choice != 5)
goto S;
这里实现了简单的计算器功能,使用goto实现循环逻辑。
11. 关闭连接
Q:
close(newsockfd);
close(sockfd);
return 0;
三、关键函数深度解析
1. socket()
int socket(int domain, int type, int protocol);
-
功能:创建通信端点
-
参数:
-
domain: 通信域(AF_INET, AF_INET6等)
-
type: 通信语义(SOCK_STREAM, SOCK_DGRAM等)
-
protocol: 通常为0,表示默认协议
-
-
返回值:成功返回文件描述符,失败返回-1
2. bind()
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
-
功能:将Socket与地址绑定
-
参数:
-
sockfd: Socket文件描述符
-
addr: 指向地址结构的指针
-
addrlen: 地址结构大小
-
-
返回值:成功返回0,失败返回-1
3. listen()
int listen(int sockfd, int backlog);
-
功能:开始监听连接请求
-
参数:
-
sockfd: Socket文件描述符
-
backlog: 等待连接队列的最大长度
-
-
返回值:成功返回0,失败返回-1
4. accept()
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
-
功能:接受连接请求
-
参数:
-
addr: 用于存储客户端地址
-
addrlen: 地址结构大小
-
-
返回值:成功返回新的Socket文件描述符,失败返回-1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
void error(const char *msg)
{
perror(msg);
exit(1);
}
int main(int argc, char *argv[])
{
if(argc < 2)
{
fprintf(stderr,"Port no not provided, Program terminated");
exit(1);
}
int sockfd, newsockfd, portno;
char buffer[255];
struct sockaddr_in serv_addr, cli_addr;
socklen_t clilen;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
error ("Error opening Socket.");
}
bzero((char *)&serv_addr, sizeof(serv_addr));
portno = atoi(argv[1]);
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(portno);
if(bind(sockfd , (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
error("Binding Failed.");
listen(sockfd,5);
clilen = sizeof(cli_addr);
newsockfd = accept(sockfd, (struct sockaddr*)&cli_addr,&clilen);
if(newsockfd < 0)
error("Error on Accept");
int num1, num2, ans, choice,n;
S: n = write(newsockfd,"Enter Number 1 : ",strlen("Enter Number 1"));
if (n < 0) error("Error writing to socket");
read(newsockfd, &num1, sizeof(int));
printf("Client – Number 1 is : %d \\n",num1);
n = write(newsockfd,"Enter Number 2 : ",strlen("Enter Number 2"));
if (n < 0) error("Error writing to socket");
read(newsockfd, &num2, sizeof(int));
printf("Client – Number 2 is : %d \\n",num2);
n = write(newsockfd,"Enter your choice: \\n1.Addition\\n2.subtraction\\n3.Multiplication\\n4.Division\\n5.Exit\\n",strlen("Enter your choice: \\n1.Addition\\n2.subtraction\\n3.Multiplication\\n4.Division\\n5.Exit\\n"));
if(n < 0) error("ERROR writing to socket");
read(newsockfd, &choice, sizeof(int));
printf("Client – choice is :%d\\n",choice);
switch (choice)
{
case 1:
ans = num1 + num2;
break;
case 2:
ans = num1 – num2;
break;
case 3:
ans = num1 * num2;
break;
case 4:
ans = num1 / num2;
break;
case 5:
goto Q;
break;
}
write(newsockfd,&ans,sizeof(int));
if(choice != 5)
goto S;
Q:
close(newsockfd);
close(sockfd);
return 0;
}
/*
filename server_ipaddress portno
argc[0] filename
argv[1] serve_ipaddress
argv[2] portno
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
void error(const char *msg)
{
perror(msg);
exit(0);
}
int main(int argc, char *argv[])
{
int sockfd, portno, n;
char buffer[256];
struct sockaddr_in serv_addr;
struct hostent *server;
if (argc < 3)
{
fprintf(stderr, "usage %s hostname port\\n", argv[0]);
exit(1);
}
portno = atoi(argv[2]);
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
error ("Error opening Socket.");
}
server = gethostbyname(argv[1]);
if(server == NULL)
{
fprintf(stderr, "Error,no such host");
}
bzero((char *)&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
bcopy((char *)server->h_addr, (char *)&serv_addr.sin_addr.s_addr, server->h_length);
serv_addr.sin_port = htons(portno);
if(connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
{
error("Connection Failed");
}
int num1, num2, choice, ans;
S:
bzero(buffer,256);
n = read (sockfd, buffer, 255);
if( n < 0 )
error("ERROR reading from socket");
printf("Server – %s\\n",buffer);
scanf("%d",&num1);
write(sockfd, &num1,sizeof(int));
bzero(buffer,256);
n = read (sockfd, buffer, 255);
if( n < 0 )
error("ERROR reading from socket");
printf("Server – %s\\n",buffer);
scanf("%d",&num2);
write(sockfd, &num2,sizeof(int));
bzero(buffer,256);
n = read (sockfd, buffer, 255);
if( n < 0 )
error("ERROR reading from socket");
printf("Server – %s\\n",buffer);
scanf("%d",&choice);
write(sockfd, &choice,sizeof(int));
if (choice == 5)
goto Q;
read(sockfd, &ans ,sizeof(int));
printf("Server- The answer is :%d\\n",ans);
if(choice !=5)
goto S;
Q:
printf("You have selected to exit.Exit Successful");
close(sockfd);
return 0;
}
一、函数参数记忆框架(TCP Socket版)
1. 核心思维:"3W1H"模型
-
Where (地址相关):sockaddr_in, addrlen
-
What (数据相关):buffer, strlen
-
Which (标识符):sockfd, newsockfd
-
How (方式):flags (通常填0)
2. 函数参数速记表
socket() | "家-门-钥匙" | AF_INET, SOCK_STREAM, 0 |
bind() | "门牌-地址本-地址长度" | sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr) |
listen() | "门牌-候客室大小" | sockfd, 5 (backlog) |
accept() | "门牌-客户登记表-表长度" | sockfd, cli_addr, &clilen |
connect() | "门牌-目的地地址-地址长度" | sockfd, serv_addr, sizeof(serv_addr) |
read()/write() | "信箱-纸条-纸条大小" | newsockfd, buffer, sizeof(buffer) |
二、参数分类记忆法
1. 地址家族三件套
serv_addr.sin_family = AF_INET; // IPv4
serv_addr.sin_addr.s_addr = INADDR_ANY; // 所有IP
serv_addr.sin_port = htons(portno); // 端口转换
-
记忆口诀:"家-门-方向牌"(family-address-port)
2. 类型转换四重奏
// 指针转换
(struct sockaddr*)&serv_addr
// 字节序转换
htons() // host to network short
htonl() // host to network long
ntohs() // network to host short
ntohl() // network to host long
-
记忆技巧:"h→n是出门,n→h是回家"
三、实战填参模板
1. 服务端标准流程
// 1. 创建socket(三固定参数)
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
// 2. 绑定地址(结构体强制转换)
bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
// 3. 接受连接(注意&clilen)
accept(sockfd, (struct sockaddr*)&cli_addr, &clilen);
2. 客户端连接模板
connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
四、常见参数填错场景
忘记地址结构体转换 | (struct sockaddr*)&serv_addr | "套外套"法则 |
传值 vs 传地址混淆 | &clilen 而不是 clilen | accept()要修改长度值 |
字节序未转换 | htons(portno) | 所有网络传输的数据都要转换 |
buffer大小计算错误 | read(fd, buf, sizeof(buf)) | 用sizeof而不是strlen |
五、调试技巧
参数检查清单:
-
所有网络字节序转换了吗?
-
地址结构体转换了吗?
-
长度参数传地址了吗?
-
错误处理都写了吗?
GDB调试命令:
(gdb) p serv_addr # 查看地址结构体
(gdb) p &serv_addr # 确认指针类型
六、类比记忆法
socket() | 买手机 | 手机类型=AF_INET |
bind() | 办手机卡 | 电话号码=portno |
listen() | 开机 | 最大未接来电=backlog |
accept() | 接听来电 | 来电显示=cli_addr |
connect() | 拨打电话 | 对方号码=serv_addr |
七、进阶记忆法(协议栈层次)
应用层:buffer数据
传输层:SOCK_STREAM/SOCK_DGRAM
网络层:AF_INET/AF_INET6
链路层:系统自动处理
记住:从上到下越来越底层,从下到上越来越抽象
评论前必须登录!
注册