TCP/UDP编程

众所周知,TCP/UDP是网络模型中传输层的两个非常重要的协议。由于该协议受操作系统管理,我们只能通过socket这一接口实现在应用层的高级语言编程。在横向对比了java、python等流行语言对socket编程的语法后,不难发现,c语言是真滴烦,***!

当然,抛开繁琐的实现逻辑,其编写过程最为严谨和完整,每写完一次都有所收获。

目录

UDP

        Client

         Server

TCP

        Client

        Server

        遇到的问题

        Linux系统的检验方案


UDP

在讨论代码之前,先把下面这张图的逻辑捋顺,代码的结构就十分清晰了,不过下面的程序中客户端和服务器都只扮演了一个独特的角色。

        Client

        客户端的目标是将执行程序时填写的数据传输到服务器上。

#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#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 <Server IP> <Echo Word>\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);
}

         Server

        服务器的目标是打印出客户端的信息和发送的数据。

#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#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

同样,先看图,该tcp程序是为了服务器向客户端传输指定的生文件(文本文件)、图像文件等,因此含有收发的角色互换。

 

        Client

        客户端的目标先是向服务器发送指定的文件,再是将该文件的数据保留本地的文件中。

#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

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 <Server IP> <File name>\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);
}

        Server

        服务器的目标先是接收客户端发来的文件名,将本地的该文件的所有数据发送给客户端。

#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

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<lseek(fd,0,SEEK_END);i++){
                                memset(echoBuffer,0,sizeof(echoBuffer));
                                lseek(fd,i*32,SEEK_SET);
                                int j = read(fd, echoBuffer,32);
                                if (send(newsock,echoBuffer,j,0) != j)
                                        printf("send Wrong number!");
                        }
                        close(fd);
                }
                //在一次连接的事务处理完后,记得关掉此次连接,这样才能处理下一个客户
                close(newsock);
        }
        close(sock);
        exit(0);
}

        遇到的问题

        在编写tcp程序的时候,考虑一种情况,即传输的文件比较大,那么服务器会不断地发送信息给客户端,这样真的没问题么?

        这里就涉及到socket自带的缓存窗口机制(知识点!!)了,客户端和服务器都会有各自收发的收发窗口,对方发送的数据会不断向窗口内填充,当你已经把窗口内的数据处理完之后,该窗口就会向后移动。所以说,在这个程序内,一旦缓存区被冲爆了,tcp的内部机制就要求对方重新发送。不过在我们这个程序中的结果就是,就是客户端挂了。。。。。

        Linux系统的检验方案

        为了测试这个程序,在Xshell内利用rz和sz+指定文件名的方式,可以将外部的jpg之类的文件传进去,再把生成的副本(根据你自己定的文件名来)从系统内拿出来,就可以检验了。

        放几张结果图:

        这里相当于将上面这张图传进去再传出来,多的不说了,let's go g2!!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值