众所周知,TCP/UDP是网络模型中传输层的两个非常重要的协议。由于该协议受操作系统管理,我们只能通过socket这一接口实现在应用层的高级语言编程。在横向对比了java、python等流行语言对socket编程的语法后,不难发现,c语言是真滴烦,***!
当然,抛开繁琐的实现逻辑,其编写过程最为严谨和完整,每写完一次都有所收获。
目录
UDP
Client
Server
TCP
Client
Server
遇到的问题
Linux系统的检验方案
在讨论代码之前,先把下面这张图的逻辑捋顺,代码的结构就十分清晰了,不过下面的程序中客户端和服务器都只扮演了一个独特的角色。
客户端的目标是将执行程序时填写的数据传输到服务器上。
#include
#include
#include
#include
#include
#include
#define ECHOMAX 255
int main(int argc,char *argv[]){
int sock;
struct sockaddr_in echoServAddr;
struct sockaddr_in fromAddr;
char *servIP;
char *echoString;
int echoStringLen;
//在执行程序时需要满足的变量输入格式
if ((argc < 3)){
printf("Usage: %s \n",argv[0]);
exit(1);
}
//输入的第一项填入服务器的IP地址,第二项是需要传的数据(加入了大小上限)
servIP = argv[1];
echoString = argv[2];
if ((echoStringLen = strlen(echoString)) > ECHOMAX){
printf("Echo Word too long.\n");
}
//声明是UDP的socket(SOCK_DGRAM),第一个参数是设置sock的结构
if ((sock = socket(PF_INET, SOCK_DGRAM ,0)) < 0)
printf("socket() failed.\n");
//对目标服务器的端口进行设置,同时需要考虑到现实网络和本地网络交流方式是不同的,例如IP地址和端口号需要进行转换
memset(&echoServAddr, 0, sizeof(echoServAddr));
echoServAddr.sin_family = AF_INET;
echoServAddr.sin_addr.s_addr = inet_addr(servIP);
echoServAddr.sin_port = htons(Port);//注意这里的端口根据你服务器的来
//将需要发送的字符串发送至上面绑定的目标位置
if ((sendto(sock, echoString, echoStringLen, 0, (struct sockaddr *) &echoServAddr, sizeof(echoServAddr))) != echoStringLen)
printf("sendto() sent a different number of bytes than expected.\n");
//记得关掉sock
close(sock);
exit(0);
}
服务器的目标是打印出客户端的信息和发送的数据。
#include
#include
#include
#include
#include
#include
#define ECHOMAX 255
int main(int argc,char *argv[]){
int sock;
struct sockaddr_in echoServAddr;
struct sockaddr_in echoClntAddr;
unsigned int cliAddrLen;
char echoBuffer[ECHOMAX];
int recvMsgSize;
//同样是生成基于UDP协议的socket
if ((sock = socket(PF_INET,SOCK_DGRAM,0)) < 0){
printf("socket failed!\n");
}
//这里在声明服务器本地的端口
memset(&echoServAddr, 0, sizeof(echoServAddr));
echoServAddr.sin_family = AF_INET;
echoServAddr.sin_addr.s_addr = htonl(INADDR_ANY);
echoServAddr.sin_port = htons(Port);//端口自己指定
//将socket绑定到上面完善的端口
if ((bind(sock, (struct sockaddr *) &echoServAddr,sizeof(echoServAddr))) < 0){
printf("bind failed!\n");
}
//保持在线^^
for(;;){
cliAddrLen = sizeof(echoClntAddr);
//接收到客户发来的数据(存在缓存中)并获取客户端的端口和IP信息(存在结构体中)
if ((recvMsgSize = recvfrom(sock, echoBuffer, ECHOMAX, 0,(struct sockaddr *) &echoClntAddr, &cliAddrLen)) < 0)
printf("recvfrom failed!\n");
//把客户端的数据、信息都打印出来(注意上面提到过的转换)
printf("./UDPsvr:from %s %d : %s\n", inet_ntoa(echoClntAddr.sin_addr), echoClntAddr.sin_port, echoBuffer);
//记得清空缓存
memset(echoBuffer, 0, sizeof(echoBuffer));
}
}
同样,先看图,该tcp程序是为了服务器向客户端传输指定的生文件(文本文件)、图像文件等,因此含有收发的角色互换。
客户端的目标先是向服务器发送指定的文件,再是将该文件的数据保留本地的文件中。
#include
#include
#include
#include
#include
#include
#include
int main(int argc,char *argv[]){
int sock;
int fd;
struct sockaddr_in echoServAddr;
char *servIP;
char *echoString;
char *filename;
char echoBuffer[32];
if ((argc < 3)){
printf("Usage: %s \n",argv[0]);
exit(1);
}
servIP = argv[1];
echoString = argv[2];
//这里声明的是基于TCP协议的socket,注意对比
if ((sock = socket(PF_INET,SOCK_STREAM,0)) < 0)
printf("socket() failed.\n");
//同样需要声明服务器的端口
memset(&echoServAddr, 0, sizeof(echoServAddr));
echoServAddr.sin_family = AF_INET;
echoServAddr.sin_addr.s_addr = inet_addr(servIP);
echoServAddr.sin_port = htons(Port);
//与UDP不同,TCP需要双方稳定的联系,因此先建立连接(写的时候偷懒没加检查)
connect(sock, (struct sockaddr*) &echoServAddr, sizeof(echoServAddr);
//发送数据(想要获取的文件)
send(sock, echoString, 32, 0);
//这里因为是在同一台机子上测试,所以想生成一个副本文件
filename = strcat(echoString,".bak");
fd = open(filename, O_RDWR | O_APPEND | O_CREAT, 0777);
//这里是文件写入的逻辑,跟socket编程无关,就不拓展了,但值得一提的是可以用c语言fwrite那套逻辑去写更加方便
int len;
while(len = recv(sock,echoBuffer,32, 0))
{
write(fd, echoBuffer, len);
memset(echoBuffer, 0, sizeof(echoBuffer));
}
//记得关掉文件和socket
close(fd);
printf("the file stored in %s.\n",filename);
close(sock);
exit(1);
}
服务器的目标先是接收客户端发来的文件名,将本地的该文件的所有数据发送给客户端。
#include
#include
#include
#include
#include
#include
#include
int main(int argc,char *argv[]){
int sock;
int fd;
struct sockaddr_in echoServAddr;
struct sockaddr_in echoClntAddr;
unsigned int cliAddrLen;
char echoBuffer[32];
if ((sock = socket(PF_INET,SOCK_STREAM,0)) < 0){
printf("socket failed!\n");
}
//服务器绑定本地端口
memset(&echoServAddr, 0, sizeof(echoServAddr));
echoServAddr.sin_family = AF_INET;
echoServAddr.sin_addr.s_addr = htonl(INADDR_ANY);
echoServAddr.sin_port = htons(Port);
if ((bind(sock, (struct sockaddr *) &echoServAddr,sizeof(echoServAddr))) < 0){
printf("bind() failed!\n");
}
//对比可发现服务器需要监听来自客户端的连接,第二个系数是队列长度(在每一次处理连接都需要时间,在同一时间多个客户端发来连接请求需要排队)
if (listen(sock, 5) < 0)
printf("listen() failed!\n");
for(;;){
cliAddrLen = sizeof(echoClntAddr);
//接收连接中的客户端的信息
int newsock = accept(sock, (struct sockaddr*) &echoClntAddr,&cliAddrLen);
//一旦服务器收到了来自客户端请求的文件的名字,执行文件数据发送逻辑,具体如何操作文件的逻辑不做展开
if (recv(newsock,echoBuffer, 32, 0)){
int fd = open(echoBuffer, O_RDONLY, 0777);
memset(echoBuffer,0,sizeof(echoBuffer));
int i;
for (i=0;i*32
在编写tcp程序的时候,考虑一种情况,即传输的文件比较大,那么服务器会不断地发送信息给客户端,这样真的没问题么?
这里就涉及到socket自带的缓存窗口机制(知识点!!)了,客户端和服务器都会有各自收发的收发窗口,对方发送的数据会不断向窗口内填充,当你已经把窗口内的数据处理完之后,该窗口就会向后移动。所以说,在这个程序内,一旦缓存区被冲爆了,tcp的内部机制就要求对方重新发送。不过在我们这个程序中的结果就是,就是客户端挂了。。。。。
为了测试这个程序,在Xshell内利用rz和sz+指定文件名的方式,可以将外部的jpg之类的文件传进去,再把生成的副本(根据你自己定的文件名来)从系统内拿出来,就可以检验了。
放几张结果图:
这里相当于将上面这张图传进去再传出来,多的不说了,let's go g2!!