目录:
- 1. 案例概述
- 2. 代码(已做详细注释)
-
- 2.1 客户端代码
- 2.2 服务端代码
- 3. 运行测试:
-
- 3.1 编译:
- 3.2 运行:
- 3.3 运行结果:
- 4. 踩坑经历:
-
- 4.1 问题:
- 4.2 解决办法:将IP地址换成127.0.0.1
1. 案例概述
采用主从模式,客户端向服务端发送自己的主机名称。该案例比较简单,目的是练习掌握熟悉套接字编程框架。
编写一个客户端和服务端,要求能够将客户端发送的信息显示出来。编译完在终端运行,端口号和IP地址可以在命令行中指定。
-
通信规程(应用层协议)
客户端将信息发送到服务端,仅发送一次,服务端无反馈。 -
服务端(udp01_s.c)
按主从模式的编程框架构建好后,主要侧重点是其数据传输部分,即通信规程的实现部分。
该服务端只负责接收数据,不向客户端反馈任何数据。 -
客户端(udp01_c.c)
与服务端类似,侧重点也是通信规程的实现部分。
客户端只发送数据,不需要接收数据。
2. 代码(已做详细注释)
2.1 客户端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
//client客户端
int main(int argc, char **argv)//argc 是指传入参数的个数,argv[] 是一个指针数组,**argv 是一个指针数组指针
{
const int SIZE_BUF = 1024;
struct sockaddr_in addr1, addr2;//结构体,用来处理网络通信的地址
socklen_t addrLen;//addrlen:dest结构体的长度,一般和sizeof()表示
int sock = -1;
char *buf = NULL;
if(argc != 3)
{
printf("Usage: %s port serverip\n", argv[0]);
return 0;
}
//fill server address填写服务器地址
bzero(&addr1, sizeof(addr1));//初始化结构体 addr1
addr1.sin_family = AF_INET;//设置地址家族
addr1.sin_port = htons(atoi(argv[1]));//设置端口
//mysock.sin_addr.s_addr = inet_addr("192.168.1.0"); //设置地址
//inet_aton 地址转换,将x.x.x.x形式的IP地址串转换为in_addr类型的结构体 成功返回1,失败返回0
if(0 == inet_aton(argv[2], &addr1.sin_addr))//如果转换失败
{
printf("server-ip is invalid.\n");
return 1;
}
sock = socket(AF_INET, SOCK_DGRAM, 0);//创建套接字(获得fd 套接字描述符)
if(sock == -1)//如果失败
{
//for linux, perror("xxx") show error description "yyy" in format xxx: yyy
perror("socket() fail ");
return 2;
}
buf = (char*)malloc(SIZE_BUF);
addrLen = sizeof(addr2);
//in block mode, wait till error occur, or data arrival
//recvfrom(sock, buf, SIZE_BUF, 0, (struct sockaddr*)&addr2, &addrLen);
sprintf(buf,"pid:%d", getpid());//将获取的进程识别码转存到buf中,并输出到屏幕上
//如果发送数据失败
if(-1 == sendto(sock, buf, strlen(buf), 0, (struct sockaddr*)&addr1, sizeof(addr1)))
{
perror("sendto() fail ");
}
else
{
printf("sendto() ok\n");
}
close(sock);//关闭套接字
free(buf);
return 0;
}
2.2 服务端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
//server
int main(int argc, char **argv)
{
const int SIZE_BUF = 1024;
char *buf = (char*)malloc(SIZE_BUF);
struct sockaddr_in addr1, addr2;
int sockfd = -1;
if(argc != 3)
{
printf("Usage: %s port serverip\n", argv[0]);
return 0;
}
bzero(&addr1, sizeof(addr1)); //初始化结构体 addr1
addr1.sin_family = AF_INET; //设置地址家族
addr1.sin_port = htons(atoi(argv[1])); //设置端口
addr1.sin_addr.s_addr = inet_addr(argv[2]); //设置地址
int inet_aton1 = inet_aton(argv[2], &addr1.sin_addr); //地址转换
if(inet_aton1 == 0) //如果转换失败
{
printf("server-ip is invalid.\n");
return 1;
}
//服务器工作过程:
// 1)创建数据报套接字
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd == -1)//如果失败
{
//for linux, perror("xxx") show error description "yyy" in format xxx: yyy
perror("socket() fail ");
return 2;
}
// 2)绑定地址和端口信息,端口需要通知所有客户端,
int bind1 = bind(sockfd, (struct sockaddr*)&addr1, sizeof(addr1));
if(bind1 == -1)
{
perror("bind() fail ");
return 3;
}
// 3)等待接收客户端数据
// 4)收到数据后,按照通信规程(也可称为业务逻辑)处理,一般要反馈
int len = sizeof(addr2);
int recvfrom1 = recvfrom(sockfd,buf,SIZE_BUF,0,(struct sockaddr*)&addr2,&len);
if(recvfrom1 == -1)
{
perror("recvfrom() fail ");
}
else
{
printf("recvfrom() ok\n");
}
// 5)在必要的情况下,关闭套接字
close(sockfd);
return 0;
}
3. 运行测试:
注意:先运行服务端代码,再运行客户端代码!!!
3.1 编译:
//服务端
gcc server2.c -o server2
//客户端
gcc client.c -o client
3.2 运行:
//服务端
./server2 1234 127.0.0.1
//客户端
./client 1234 127.0.0.1
3.3 运行结果:
4. 踩坑经历:
4.1 问题:
第一次在终端输入的地址是192.168.0.1,程序始终无法运行,客户端可以发送数据包,服务端一直显示bind()失败。
后来才明白:默认情况下,登陆路由器管理界面的地址是192.168.0.1或192.168.1.1。即192.168.0.1已经和路由器的LAN端口进行绑定,所以会一直bind()失败。
4.2 解决办法:将IP地址换成127.0.0.1
127.0.0.1是回送地址,指本地机,一般用来测试使用。回送地址(127.x.x.x)是本机回送地址(Loopback Address),即主机IP堆栈内部的IP地址,主要用于网络软件测试以及本地机进程间通信,无论什么程序,一旦使用回送地址发送数据,协议软件立即返回,不进行任何网络传输。