网络编程(udp tcp)
组播通讯:
- 发送端实现步骤:
- 创建 UDP 类型的套接字
- 设置组播地址和组播端口
- 向组播地址和组播端口发送数据
- 可以接收回复的数据
- 关闭套接字
2.接收端实现步骤:
1.创建UDP类型的套接字
2.绑定任意IP,组播端口到套接字上
3.加入组播组(设置套接字 IPPROTO_IP层 IP_ADD_MEMBERSHIP属性)
struct ip_mreqn mreq;
mreq.imr_multiaddr = xxxx
mreq.imr_address = xxxxx;
mreq.imr_ifindex = if_nametoindex("ens33");
//设置套接字选项
setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq,
sizeof(mreq));
4.接收组播数据
5.可以向发送端回复网络数据
6.离开组播组
setsockopt(sockfd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq,
sizeof(mreq));
7.关闭套接字
UDP 广播通讯
广播通讯只能由UDP来完成。广播通讯要借助广播地址。
广播地址:
1. 直接广播地址 (主机ID数据位全为1) 例: 192.168.11.255
2. 本地广播地址 (网络ID和主机ID数据位全为1) 例: 255.255.255.255
广播通讯的流程:
1. 发送端
- 创建UDP类型套接字
2) 设置套接字的广播属性
setsockopt
头文件: #include <sys/socket.h>
函数原型: int setsockopt(int sockfd,int level, int optname ,
const void *optval,socklen_t optlen);
函数功能: 接收网络数据
函数参数: sockfd: 套接字描述符
level: 指定控制套接字的层次.可以取三种值:
1) SOL_SOCKET: 通用(应用层)套接
字选项.
2) IPPROTO_IP: IP选项(网络层).
3) IPPROTO_TCP: TCP选项(传输层).
optname: 待设置的属性名
optval: 指向属性值的指针
optlen: 属性值的长度:
函数返回值:成功返回 0
失败返回-1. 错误码存储在errno
例子:
int enable = 1;
setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&enable,sizeof(int));
3) 向广播地址/端口进行数据发送。
4) 接收网络回复数据;
5) 关闭套接字
2. 接收端
1) 创建UDP类型套接字
2) 绑定任意IP/广播端口到套接字上
3) 接收广播数据;
4) 向发送端回复网络数据
5) 关闭套接字
UDP组播通讯
组播通讯只能由UDP来完成。组播通讯要借助组播地址。
组播地址:D类IP主要用于组播通讯:
永久组播地址: 224.0.0.1 ~ 224.0.0.255
公用组播地址: 224.0.1.0 ~ 224.0.1.255
函数原型: int listen(int sockl,int backlog);
函数功能: 接收网络数据
函数参数: sockl: 监听套接字描述符
backlog: 可排队连接的最大连接数
函数返回值:成功返回0
失败返回-1. 错误码存储在errno
4. 接受客户端连接,并生成通讯套接字
accept
头文件: #include <sys/socket.h>
函数原型: int accept(int sockl,struct sockaddr* addr,int*
socklen);
函数功能: 接收客户端;连接
函数参数: sockl: 监听套接字描述符
addr: 指向地址结构体指针,用来获取客户端地址信息
socklen: 获取客户端地址信息 结构体长度
函数返回值:成功返回新的套接字。用于与客户端数据收发
失败返回-1. 错误码存储在errno
5. 与客户端进行数据的收发通讯
send
头文件: #include <sys/socket.h>
函数原型: int sendto(int sockfd,void* buf,int len ,int flags);
函数功能: 发送网络数据
函数参数: sockfd: 套接字描述符
buf: 内存缓冲区地址,用于存储待发送的网络数据
len: 待发送的数据长度
flags: 操作标志,一般写为0
函数返回值:成功返回实际发送的数据长度(字节为单位)
失败返回-1. 错误码存储在errno
recv
头文件: #include <sys/socket.h>
函数原型: int recv(int sockfd,void* buf,int len ,int flags);
函数功能: 接收网络数据
函数参数: sockfd: 套接字描述符
buf: 内存缓冲区地址,用于存储接收到的网络数据
len: 内存缓冲区长度
flags: 操作标志,一般写为0
from: 指向地址结构体的指针,用于接收通讯对方的地址信息:
fromlen: 用于接收对方地址结构体的长度
综合案例
-
代码功能概述
该代码实现了一个简单的广播发送端程序,通过 UDP 协议向指定网络中的广播地址发送用户输入的数据,直到用户输入 “quit” 为止。 -
代码结构分析
头文件和预处理
#include “header.h”:包含自定义头文件(未提供具体内容),可能定义了 SockAddr、SockAddrIn 等类型。
缺少标准库头文件:
需要手动添加以下头文件:
#include <stdio.h> // 用于输入输出(puts、printf)
#include <stdlib.h> // 用于字符串转数字(atoi)
#include <string.h> // 用于字符串操作(strcmp、strlen、memset)
#include <unistd.h> // 用于系统调用(close)
#include <arpa/inet.h> // 用于网络地址转换(inet_addr、htons)
#include <sys/socket.h> // 用于套接字操作(socket、setsockopt、sendto)
主函数
命令行参数检查:
如果参数数量少于 3,输出提示并退出。
第 1 个参数(argv[1]):广播地址(如 192.168.12.255 或 255.255.255.255)。
第 2 个参数(argv[2]):广播端口号。
打印命令行参数:
使用 printf 输出广播地址和端口号。
创建套接字:
使用 socket(AF_INET, SOCK_DGRAM, 0) 创建一个 UDP 套接字。
如果创建失败,输出错误信息并退出。
设置广播模式:
使用 setsockopt 开启套接字的广播模式。
参数说明:
SOL_SOCKET:操作级别(套接字级别)。
SO_BROADCAST:启用广播模式。
&is_enable:设置为 1,表示启用广播。
如果设置失败,输出错误信息并关闭套接字后退出。
配置广播地址:
使用 SockAddrIn 结构体配置广播地址信息。
sin_family:设置为 AF_INET,表示 IPv4 协议。
sin_port:使用 htons 将端口号转换为网络字节序。
sin_addr.s_addr:使用 inet_addr 将广播地址字符串转换为二进制格式。
发送广播数据:
进入一个无限循环,等待用户输入。
使用 fgets 从标准输入读取用户输入的数据。
使用 strlen 删除输入数据中的换行符。
使用 sendto 将数据发送到广播地址。
如果用户输入 “quit”,跳出循环。
使用 memset 清空发送缓冲区。
关闭套接字:
在退出循环后,使用 close 关闭套接字。
3. 代码问题与改进建议
问题
缺少头文件:
代码中未包含必要的头文件,可能导致编译错误。
缓冲区溢出风险:
msg[strlen(msg) - 1] = 0; 如果 msg 为空字符串(如直接回车),会导致数组越界。
广播地址处理不灵活:
代码只支持从命令行参数传入广播地址,未支持默认广播地址(如 255.255.255.255)。
SockAddrIn 定义不明:
代码中使用了 SockAddrIn,但未提供其定义,可能是对 struct sockaddr_in 的别名。
sendto 参数问题:
sendto 的第三个参数 sizeof(msg) 可能导致发送多余的空字节,应使用 strlen(msg)。
改进建议
添加必要的头文件:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
修复缓冲区溢出问题:
if (msg[strlen(msg) - 1] == '\n')
msg[strlen(msg) - 1] = '\0';
支持默认广播地址:
if (strcmp(argv[1], "default") == 0)
send_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST);
else
send_addr.sin_addr.s_addr = inet_addr(argv[1]);
明确 SockAddrIn 定义:
typedef struct sockaddr_in SockAddrIn;
#include "header.h"
/*
广播的发送端:
1.调用setsockopt开启广播模式
2.设置广播的特殊IP地址: 192.168.12.255 或者 255.255.255.255
3.广播的发送端只做发送
*/
int main(int argc, char const *argv[])
{
// 通过main函数的参数传递ip和端口
if (argc < 3)
{
puts("命令行缺少参数!");
return -1;
}
// 打印命令行参数
printf("%s--%s\n", argv[1], argv[2]);
// 创建发送端的套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == sockfd)
{
perror("socket failed");
return -1;
}
// 1.调用setsockopt开启广播模式
int is_enable = 1;
int r = setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &is_enable, sizeof(is_enable));
if (-1 == r)
{
perror("setsockopt failed");
close(sockfd);
return -1;
}
// 2.设置广播的特殊IP地址: 192.168.12.255 或者 255.255.255.255
// 定义地址结构体变量
SockAddrIn send_addr;
// ip4协议
send_addr.sin_family = AF_INET;
send_addr.sin_port = htons(atoi(argv[2]));
// 192.168.12.255 或者 255.255.255.255
send_addr.sin_addr.s_addr = inet_addr(argv[1]);
// send_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST); //255.255.255.255
// 3.广播的发送端只做发送
// 定义要发送的数据
char msg[64] = {0};
while (1)
{
puts("请输入要发送的内容:");
fgets(msg, sizeof(msg), stdin);
// 删除最后的换行符
msg[strlen(msg) - 1] = 0; // '\0'
// 调用sendto函数发送数据
sendto(sockfd, msg, sizeof(msg), 0, (SockAddr *)&send_addr, sizeof(send_addr));
// 当用户输入quit是退出发送端
if (strcmp("quit", msg) == 0)
{
break;
}
// 清理数据
memset(msg, 0, sizeof(msg));
}
// 关闭套接字
close(sockfd);
return 0;
}
#include "header.h"
/*
广播接收端:
1.广播接收端是被动接收,只做接收,不用其他设置
*/
int main(int argc, char const *argv[])
{
// 通过命令行传递ip和端口
if (argc < 2)
{
puts("命令行缺少参数");
return -1;
}
// 测试打印参数
printf("%s\n", argv[1]);
// 创建接收端的套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == sockfd)
{
perror("socket failed");
return -1;
}
// 定义地址结构体
SockAddrIn recv_addr = {0};
// ip4协议
recv_addr.sin_family = AF_INET;
// 端口
recv_addr.sin_port = htons(atoi(argv[1]));
// ip地址 INADDR_ANY : 0.0.0.0
recv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // ip地址是:0.0.0.0 表示任意一个ip地址
//可以通过netstat -tunl 命令查看网络ip地址与端口的占用状态
// 绑定地址
int r = bind(sockfd, (SockAddr *)&recv_addr, sizeof(recv_addr));
if (-1 == r)
{
perror("bind failed");
close(sockfd);
return -1;
}
// 循环接收数据
char buf[64] = {0};
// 定义广播发送端的地址结构体变量
SockAddrIn broadcast_send_addr = {0};
ssize_t size = -1;
while (1)
{
socklen_t len = sizeof(broadcast_send_addr);
size = recvfrom(sockfd, buf, sizeof(buf), 0, (SockAddr *)&broadcast_send_addr, &len);
// 打印接收到的数据
char *ip = inet_ntoa(broadcast_send_addr.sin_addr);
printf("收到%s发送过来的%ld字节是数据是:%s\n", ip, size, buf);
// 清空缓冲区
memset(buf, 0, sizeof(buf));
//清空本次地址信息
memset(&broadcast_send_addr, 0, sizeof(broadcast_send_addr));
}
// 关闭套接字
close(sockfd);
return 0;
}
-
代码功能概述
该代码实现了一个组播接收端程序,通过加入组播组(如 239.0.0.2)接收组播数据,并实时打印发送者的 IP 地址和接收到的数据。 -
代码结构分析
头文件和预处理
#include “header.h”:包含自定义头文件(未提供具体内容),可能定义了 SockAddr、SockAddrIn 等类型。
缺少标准库头文件:
需要手动添加以下头文件:
#include <stdio.h> // 用于输入输出(puts、printf)
#include <stdlib.h> // 用于字符串转数字(atoi)
#include <string.h> // 用于字符串操作(memset)
#include <unistd.h> // 用于系统调用(close)
#include <arpa/inet.h> // 用于网络地址转换(inet_pton、inet_ntoa、htons)
#include <sys/socket.h> // 用于套接字操作(socket、bind、recvfrom)
#include <net/if.h> // 用于网卡索引操作(if_nametoindex)
主函数
命令行参数检查:
如果参数数量少于 3,输出提示并退出。
第 1 个参数(argv[1]):本机 IP 地址(组播组成员的 IP)。
第 2 个参数(argv[2]):组播端口号。
打印命令行参数:
使用 printf 输出本机 IP 地址和端口号。
创建套接字:
使用 socket(AF_INET, SOCK_DGRAM, 0) 创建一个 UDP 套接字。
如果创建失败,输出错误信息并退出。
绑定套接字:
使用 SockAddrIn 结构体配置绑定地址信息。
sin_family:设置为 AF_INET,表示 IPv4 协议。
sin_port:使用 htons 将端口号转换为网络字节序。
sin_addr.s_addr:设置为 INADDR_ANY,表示接收任意 IP 地址发送的数据。
使用 bind 将地址绑定到套接字。如果绑定失败,输出错误信息并关闭套接字后退出。
加入组播组:
使用 struct ip_mreqn 结构体配置组播组信息。
imr_multiaddr:设置组播组的 IP 地址(239.0.0.2)。
imr_address:设置组播组成员的 IP 地址(从命令行参数获取)。
imr_ifindex:使用 if_nametoindex 获取网卡索引(如 ens33)。
使用 setsockopt 将套接字加入组播组。
接收组播数据:
进入一个无限循环,使用 recvfrom 接收组播数据。
使用 inet_ntoa 解析发送者的 IP 地址。
打印发送者的 IP 地址和接收到的数据。
使用 memset 清空接收缓冲区。
退出逻辑:
程序无退出条件(除非手动终止),因此不会执行到移除组播组和关闭套接字的代码。
3. 代码问题与改进建议
问题
缺少头文件:
代码中未包含必要的头文件,可能导致编译错误。
无限循环:
程序无限循环,无法正常退出,移除组播组和关闭套接字的代码无法执行。
mreqn.imr_ifindex 未处理错误:
if_nametoindex 可能返回 0(表示网卡不存在),但未处理这种情况。
缓冲区溢出风险:
recvfrom 的缓冲区大小为 64 字节,未处理数据超过缓冲区的情况。
SockAddrIn 定义不明:
代码中使用了 SockAddrIn,但未提供其定义,可能是对 struct sockaddr_in 的别名。
改进建议
添加必要的头文件:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <net/if.h>
添加退出条件:
例如,收到特定消息(如 “quit”)时退出循环。
处理 if_nametoindex 错误:
mreqn.imr_ifindex = if_nametoindex("ens33");
if (mreqn.imr_ifindex == 0) {
perror("if_nametoindex failed");
close(sockfd);
return -1;
}
优化缓冲区处理:
使用动态缓冲区或设置最大接收长度。
明确 SockAddrIn 定义:
typedef struct sockaddr_in SockAddrIn;
#include "header.h"
/*
组播的接收端:
1.绑定任意发送端的ip到接收端的套接字上
2.添加组播组(设置套接字的选项) IP_ADD_MEMBERSHIP struct ip_mreqn mreqn;
2.1:设置组播组的ip mreqn.imr_multiaddr
2.2: 设置组播组成员的ip mreqn.imr_address
2.3: 通过网卡名称获取索引并设置 mreqn.imr_ifindex
3.移除组播组 IP_ADD_MEMBERSHIP
*/
int main(int argc, char const *argv[])
{
// 通过命令行传递参数
if (argc < 3)
{
puts("命令行缺少参数");
return -1;
}
printf("%s---%s\n", argv[1], argv[2]);
// 创建套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == sockfd)
{
perror("socket failed");
return -1;
}
// 1.绑定任意发送端的ip到接收端的套接字上
SockAddrIn multiAddr;
multiAddr.sin_family = AF_INET;
multiAddr.sin_port = htons(atoi(argv[2]));
// 设置接收任意ip地址发送的组播信息
multiAddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0 通配ip,代表所有ip地址
// 绑定地址到当前套接字上
int r = bind(sockfd, (SockAddr *)&multiAddr, sizeof(multiAddr));
if (-1 == r)
{
perror("bind failed");
close(sockfd);
return -1;
}
// 2.添加组播组(设置套接字的选项)
// 定义组播相关的结构体
struct ip_mreqn mreqn;
// 设置组播组的ip地址: 239.0.0.0~239.255.255.255
inet_pton(AF_INET, "239.0.0.2", &mreqn.imr_multiaddr);
// mreqn.imr_multiaddr.s_addr = inet_addr("239.0.0.2");
// 设置组播组成员的ip地址
inet_pton(AF_INET, argv[1], &mreqn.imr_address);
// 设置网卡的索引
// mreqn.imr_ifindex = 2;
mreqn.imr_ifindex = if_nametoindex("ens33");
printf("index: %d\n", if_nametoindex("ens33"));
// 设置套接字选项
setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreqn, sizeof(mreqn));
// 接收数据
char buf[64] = {0};
ssize_t size = -1;
// 定义地址结构体,用于接收发送数据的地址信息
SockAddrIn sendAddr;
while (1)
{
socklen_t len = sizeof(sendAddr);
// 接收数据
size = recvfrom(sockfd, buf, sizeof(buf), 0, (SockAddr *)&sendAddr, &len);
// 解析ip地址
char *ip = inet_ntoa(sendAddr.sin_addr);
// 打印接收到的数据
printf("收到%s发送过来长度为%ld的信息是: %s\n", ip, size, buf);
// 清除缓冲数据
memset(buf, 0, sizeof(buf));
}
// 3.移除组播组
// 将当前组播中的成员移除
setsockopt(sockfd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreqn, sizeof(mreqn));
// 关闭套接字
close(sockfd);
return 0;
}
#include "header.h"
/*
组播的发送端:
1.设置组播发送端的地址和端口
2.发送数据
*/
int main(int argc, char const *argv[])
{
// 通过命令传递参数
if (argc < 3)
{
puts("命令行缺少参数");
return -1;
}
printf("%s---%s\n", argv[1], argv[2]);
// 创建套接字socket
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == sockfd)
{
perror("socket failed");
return -1;
}
// 1.设置组播发送端的地址和端口
SockAddrIn multiAddr;
multiAddr.sin_family = AF_INET;
multiAddr.sin_port = htons(atoi(argv[2]));
// multiAddr.sin_addr.s_addr = inet_addr(argv[1]);
// multiAddr.sin_addr.s_addr = htonl(INADDR_BROADCAST); //255.255.255.255
// 设置ip地址: 可以将字符串按照协议格式转换成32位整数并设置给结构体成员
inet_pton(AF_INET, argv[1], &(multiAddr.sin_addr));
// 2.发送数据
// 定义字符串
char msg[64] = {0};
while (1)
{
puts("请输入要发送的内容:");
fgets(msg, sizeof(msg), stdin);
// 去除换行符
msg[strlen(msg) - 1] = 0;
sendto(sockfd, msg, strlen(msg), 0, (SockAddr *)&multiAddr, sizeof(multiAddr));
// 当用户输入quit时退出
if (strcmp("quit", msg) == 0)
{
break;
}
// 清除数据缓冲区
memset(msg, 0, sizeof(msg));
}
// 关闭套接字
close(sockfd);
return 0;
}
-
代码功能概述
该代码实现了一个简单的 TCP 服务端程序,监听指定 IP 地址和端口,等待客户端连接。客户端连接后,接收客户端发送的数据并回显给客户端。 -
代码结构分析
头文件和预处理
#include “header.h”:包含自定义头文件(未提供具体内容),可能定义了 SockAddr、SockAddrIn 等类型。
缺少标准库头文件:
需要手动添加以下头文件:
#include <stdio.h> // 用于输入输出(puts、printf)
#include <stdlib.h> // 用于字符串转数字(atoi)
#include <string.h> // 用于字符串操作(strlen)
#include <unistd.h> // 用于系统调用(close)
#include <arpa/inet.h> // 用于网络地址转换(inet_addr、inet_ntoa、htons、ntohs)
#include <sys/socket.h> // 用于套接字操作(socket、bind、listen、accept、recv、send)
主函数
命令行参数检查:
如果参数数量少于 3,输出提示并退出。
第 1 个参数(argv[1]):服务端 IP 地址。
第 2 个参数(argv[2]):服务端端口号。
打印命令行参数:
使用 printf 输出服务端 IP 地址和端口号。
创建套接字:
使用 socket(AF_INET, SOCK_STREAM, 0) 创建一个 TCP 套接字。
如果创建失败,输出错误信息并退出。
绑定地址:
使用 SockAddrIn 结构体配置绑定地址信息。
sin_family:设置为 AF_INET,表示 IPv4 协议。
sin_port:使用 htons 将端口号转换为网络字节序。
sin_addr.s_addr:使用 inet_addr 将 IP 地址字符串转换为二进制格式。
使用 bind 将地址绑定到套接字。如果绑定失败,输出错误信息并关闭套接字后退出。
监听客户端:
使用 listen 将套接字设置为监听状态,最大连接数为 2。
如果监听失败,输出错误信息并关闭套接字后退出。
接收客户端连接:
使用 accept 接收客户端的连接。
如果接收失败,输出错误信息并关闭套接字后退出。
成功连接后,打印提示信息。
接收客户端数据:
使用 recv 接收客户端发送的数据。
使用 inet_ntoa 和 ntohs 解析客户端的 IP 地址和端口号。
打印接收到的数据和客户端地址。
发送响应给客户端:
使用 send 将接收到的数据原样返回给客户端。
关闭套接字:
使用 close 关闭客户端和服务端套接字。
/*
TCP网络通讯的服务端
*/
#include "header.h"
int main(int argc, char const *argv[])
{
if (argc < 3)
{
puts("命令行缺少参数");
return -1;
}
printf("%s---%s\n", argv[1], argv[2]);
// 1.创建socket
int s_sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == s_sockfd)
{
perror("socket failed");
return -1;
}
// 2.绑定自己的地址
SockAddrIn serverAddr;
// ip4协议
serverAddr.sin_family = AF_INET;
// 端口 --- arv[2]
serverAddr.sin_port = htons(atoi(argv[2]));
// ip -- argv[1]
serverAddr.sin_addr.s_addr = inet_addr(argv[1]);
// 调用bind函数
int r = bind(s_sockfd, (SockAddr *)&serverAddr, sizeof(serverAddr));
if (-1 == r)
{
perror("bind failed");
close(s_sockfd);
return -1;
}
// 3.监听客户端
int r2 = listen(s_sockfd, 2);
if (-1 == r)
{
perror("listen failed");
close(s_sockfd);
return -1;
}
// 4.接收客户端的连接
SockAddrIn clientAddr;
socklen_t len = sizeof(clientAddr);
// accept有阻塞效果,当和客户端完成三次握手建立连接后,该函数结束阻塞状态,并返回客户端的套接字描述符
int c_sockfd = accept(s_sockfd, (SockAddr *)&clientAddr, &len);
if (-1 == c_sockfd)
{
perror("accept failed");
close(s_sockfd);
return -1;
}
puts("客户端已经和服务端完成了三次握手并连接上了");
// 5.接收客户端发送过来的数据
char buf[64] = {0};
// recv 有阻塞效果,当客户端没有调用send发送数据时,该函数下方的代码不执行
ssize_t size = recv(c_sockfd, buf, sizeof(buf), 0);
puts("等待客户端发送数据");
// 打印接收到的数据和客户端的地址
char *ip = inet_ntoa(clientAddr.sin_addr);
unsigned short port = ntohs(clientAddr.sin_port);
printf("[%s:%d]发送过来了%ld个字节的数据是 %s\n", ip, port, size, buf);
// 6.给客户端做出响应:服务端发送数据给客户端
send(c_sockfd, buf, strlen(buf), 0);
// 7.关闭套接字
close(c_sockfd);
close(s_sockfd);
return 0;
}
-
代码功能概述
该代码实现了一个简单的 TCP 客户端程序,连接到指定的服务端 IP 地址和端口,发送用户输入的数据,并接收服务端的响应。 -
代码结构分析
头文件和预处理
#include “header.h”:包含自定义头文件(未提供具体内容),可能定义了 SockAddr、SockAddrIn 等类型。
缺少标准库头文件:
需要手动添加以下头文件:
#include <stdio.h> // 用于输入输出(puts、printf、fgets)
#include <stdlib.h> // 用于字符串转数字(atoi)
#include <string.h> // 用于字符串操作(strlen)
#include <unistd.h> // 用于系统调用(close)
#include <arpa/inet.h> // 用于网络地址转换(inet_addr、htons)
#include <sys/socket.h> // 用于套接字操作(socket、connect、send、recv)
主函数
命令行参数检查:
如果参数数量少于 3,输出提示并退出。
第 1 个参数(argv[1]):服务端 IP 地址。
第 2 个参数(argv[2]):服务端端口号。
打印命令行参数:
使用 printf 输出服务端 IP 地址和端口号。
创建套接字:
使用 socket(AF_INET, SOCK_STREAM, 0) 创建一个 TCP 套接字。
如果创建失败,输出错误信息并退出。
连接服务端:
使用 SockAddrIn 结构体配置服务端地址信息。
sin_family:设置为 AF_INET,表示 IPv4 协议。
sin_port:使用 htons 将端口号转换为网络字节序。
sin_addr.s_addr:使用 inet_addr 将 IP 地址字符串转换为二进制格式。
使用 connect 连接到服务端。如果连接失败,输出错误信息并关闭套接字后退出。
发送消息:
使用 fgets 从标准输入读取用户输入的数据。
使用 strlen 删除输入数据中的换行符。
使用 send 将数据发送到服务端。
接收服务端响应:
使用 recv 接收服务端发送的数据。
打印服务端的 IP 地址、端口号和响应数据。
关闭套接字:
使用 close 关闭客户端套接字。
3. 代码问题与改进建议
问题
缺少头文件:
代码中未包含必要的头文件,可能导致编译错误。
未处理发送数据错误:
send 可能返回 -1,但未处理这种情况。
未处理接收数据错误:
recv 可能返回 -1(表示错误)或 0(表示服务端断开连接),但未处理这种情况。
缓冲区溢出风险:
msg[strlen(msg) - 1] = 0; 如果 msg 为空字符串(如直接回车),会导致数组越界。
SockAddrIn 定义不明:
代码中使用了 SockAddrIn,但未提供其定义,可能是对 struct sockaddr_in 的别名。
/*
TCP网络通讯的客户端:
*/
#include "header.h"
int main(int argc, char const *argv[])
{
if (argc < 3)
{
puts("命令行缺少参数");
return -1;
}
printf("%s---%s\n", argv[1], argv[2]);
// 1.创建套接字
int c_sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == c_sockfd)
{
perror("socket failed");
return -1;
}
// 2.连接服务器
SockAddrIn sockAddr;
// ipv4
sockAddr.sin_family = AF_INET;
// 端口
sockAddr.sin_port = htons(atoi(argv[2]));
// ip地址
sockAddr.sin_addr.s_addr = inet_addr(argv[1]);
int r = connect(c_sockfd, (SockAddr *)&sockAddr, sizeof(sockAddr));
if (-1 == r)
{
perror("connet failed");
close(c_sockfd);
return -1;
}
// 3.发送消息到服务器
char msg[64] = {0};
puts("请输入要发送的信息:");
fgets(msg, sizeof(msg), stdin);
// 去除换行符
msg[strlen(msg) - 1] = 0;
// 发送数据
send(c_sockfd, msg, strlen(msg), 0);
// 4.接收服务端的信息
char data[64] = {0};
recv(c_sockfd, data, sizeof(data), 0);
printf("[%s:%d]响应回来的信息是 %s\n", argv[1], atoi(argv[2]), data);
// 5.关闭套接字
close(c_sockfd);
return 0;
}