Linux网络编程:Socket编程接口(2)TCP下的网络编程

3. 完成TCP网络编程

三次挥手的阶段:
在这里插入图片描述四次挥手的阶段:
在这里插入图片描述

3.1 完善

  1. 从终端输入 ip 地址和端口号,使用 int argc 和 char* argv[]
  2. 需要多条信息的发送和接收,使用无限循环
  3. 为了不受输入堵塞的影响,让客户端和服务器之间多发消息,收发信息用各自的进程
    客户端开一个进程,让父进程写子进程读
    服务器也开一个进程,让父进程读子进程写
    (或者用线程 thread,线程创建要在前面,并使用 detach 让系统回收资源)
  4. 在一边提前关闭后,会出现另一边无限循环读取,为避免这种情况
    对 read 的返回值判断,如果是0,表示对方已经提前关闭,直接退出循环

客户端:

#include <iostream>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>    // AF_INET  sockaddr_in
#include <unistd.h>      // write() read() close()
#include <thread>
using namespace std;

int main(int argc,char* argv[]){
        if(3 != argc){
                printf("argument error\n");
                printf("Usage:%s server_ip server_port\n",argv[0]);
                return 1;
        }

        int fd = socket(AF_INET,SOCK_STREAM,0);
        if(-1 == fd){
                perror("open socket error");
                return 1;
        }

        sockaddr_in addr;
        addr.sin_family = AF_INET;
        inet_aton(argv[1],&addr.sin_addr);
        addr.sin_port = htons(atoi(argv[2]));    // atoi把字符串转为数字         
        int res = connect(fd,(sockaddr*)&addr,sizeof(addr));
        if(-1 == res){
                perror("connect error");
                return 1;
        }

        // 创建子线程
        thread t( [fd](){
                for(;;){
                        char buffer[256] = {0};
                        int n = read(fd,buffer,256);          // block
                        if(0 == n){
                                printf("server exit\n");
                                break;
                        }
                        cout << buffer << endl;
                }
        } );
        t.detach();

        // 主线程
        string s;
        while(cin >> s){     // block
                write(fd,s.c_str(),s.size()+1);
        }

        close(fd);
}

服务器:

#include <iostream>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <thread>
using namespace std;

int main(int argc,char* argv[]){
        if(3 != argc){
                printf("argument error\n");
                printf("Usage:%s server_ip server_port\n",argv[0]);
                return 1;
        }

        int fd = socket(AF_INET,SOCK_STREAM,0);
        if(-1 == fd){
                perror("open socket error");
                return 1;
        }

        sockaddr_in addr;
        addr.sin_family = AF_INET;
        inet_aton(argv[1],&addr.sin_addr);
        addr.sin_port = htons(atoi(argv[2]));
        int res = bind(fd,(sockaddr*)&addr,sizeof(addr));
        if(-1 == res){
                perror("bind error");
                return 1;
        }

        res = listen(fd,4);
        if(-1 == res){
                perror("listen error");
                return 1;
        }

        int connfd = accept(fd,NULL,NULL);      // block,堵塞,等待客户端连接
        if(-1 == connfd){
                perror("accept error");
                return 1;
        }

        // 创建子线程
        thread t( [connfd](){
                // 无限循环可多次写入和读取
                for(;;){
                        char buffer[256] = {0};
                        int n = read(connfd,buffer,256);    // block
                        if(0 == n){
                                printf("client exit\n");
                                break;
                        }
                        cout << buffer << endl;
                }
        } );
        t.detach();
        // 主线程
        string s;
        while(cin >> s){        // block
                write(connfd,s.c_str(),s.size()+1);
        }

        close(connfd);
        close(fd);
}

结果为:
在这里插入图片描述
在这里插入图片描述

3.2 属性

设置属性:
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen)

参数含义eg.设置端口释放
sockfd套接字描述符监听描述符 fd
level选项层次SOL_SOCKET 通用套接字选项
optname选项SO_REUSEADDR 让端口释放后立即就可以被再次使用,一个端口释放后会等待两分钟之后才能再被使用
optval选项值指针flag值设为1的指针 &flag
optlenoptval缓冲区长度flag的长度 sizeof(flag)
  1. 让端口释放后立即就可以被再次使用
  2. 在服务器显示客户端 IP 和端口号

服务器:

#include <iostream>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <thread>
using namespace std;

int main(int argc,char* argv[]){
        if(3 != argc){
                printf("argument error\n");
                printf("Usage:%s server_ip server_port\n",argv[0]);
                return 1;
        }

        int fd = socket(AF_INET,SOCK_STREAM,0);
        if(-1 == fd){
                perror("open socket error");
                return 1;
        }

        // 设置属性:端口释放
        int flag = 1;
        setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&flag,sizeof(flag));

        sockaddr_in addr;
        addr.sin_family = AF_INET;
        inet_aton(argv[1],&addr.sin_addr);
        addr.sin_port = htons(atoi(argv[2]));
        int res = bind(fd,(sockaddr*)&addr,sizeof(addr));
        if(-1 == res){
                perror("bind error");
                return 1;
        }

        res = listen(fd,4);
        if(-1 == res){
                perror("listen error");
                return 1;
        }

        sockaddr_in remote_addr;         // IPv4套接字地址结构
        socklen_t len = sizeof(remote_addr);
        int connfd = accept(fd,(sockaddr*)&remote_addr,&len);      // block
        cout << inet_ntoa(remote_addr.sin_addr) << ":" << ntohs(remote_addr.sin_port) << endl;
        // 整型转换为字符串,网络序转主机序
        if(-1 == connfd){
                perror("accept error");
                return 1;
        }

        thread t( [connfd](){
                for(;;){
                        char buffer[256] = {0};
                        int n = read(connfd,buffer,256);    // block
                        if(0 == n){
                                printf("client exit\n");
                                break;
                        }
                        cout << buffer << endl;
                }
        } );
        t.detach();
        // 主线程
        string s;
        while(cin >> s){        // block
                write(connfd,s.c_str(),s.size()+1);
        }

        close(connfd);
        close(fd);
}

连接后服务器端结果:

[root@foundation1 C++7.18]# g++ server2.cpp -o server2 -pthread
[root@foundation1 C++7.18]# ./server2 0.0.0.0 8081
127.0.0.1:56998

3.3 多个客户端的和单个服务器通信

每来一个客户端,服务器就创建一个线程

服务器:

#include <iostream>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <thread>
#include <vector>
using namespace std;

int main(int argc,char* argv[]){
        if(3 != argc){
                printf("argument error\n");
                printf("Usage:%s server_ip server_port\n",argv[0]);
                return 1;
        }

        int fd = socket(AF_INET,SOCK_STREAM,0);
        if(-1 == fd){
                perror("open socket error");
                return 1;
        }

        // 设置属性:端口释放
        int flag = 1;
        setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&flag,sizeof(flag));

        sockaddr_in addr;
        addr.sin_family = AF_INET;
        inet_aton(argv[1],&addr.sin_addr);
        addr.sin_port = htons(atoi(argv[2]));
        int res = bind(fd,(sockaddr*)&addr,sizeof(addr));
        if(-1 == res){
                perror("bind error");
                return 1;
        }

        res = listen(fd,4);
        if(-1 == res){
                perror("listen error");
                return 1;
        }

        vector<int> fds;           // 建立一个vector
        // 服务器对每个客户端都发信息
        thread writer( [&](){
                string s;
                while(cin >> s){        // block
                        for(auto connfd:fds)
                                write(connfd,s.c_str(),s.size()+1);
                }
        } );
        writer.detach();

        // 没来一个客户端,就需要建立一个connfd
        for(;;){
        sockaddr_in remote_addr;
        socklen_t len = sizeof(remote_addr);
        int connfd = accept(fd,(sockaddr*)&remote_addr,&len);      // block
        cout << inet_ntoa(remote_addr.sin_addr) << ":" << ntohs(remote_addr.sin_port) << endl;
        if(-1 == connfd){
                perror("accept error");
                return 1;
        }
        fds.push_back(connfd);       // 收所有客户端的connfd

        thread t( [connfd](){
                for(;;){
                        char buffer[256] = {0};
                        int n = read(connfd,buffer,256);    // block
                        if(0 == n){
                                printf("client exit\n");
                                break;
                        }
                        cout << buffer << endl;
                }
        } );
        t.detach();
        }

        for(auto connfd:fds) close(connfd);
        close(fd);
}

结果为:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.4 代码格式化工具astyle

  1. 下载
  2. 解压 tar -zxvf astyle安装包
    在这里插入图片描述
  3. 进入编译目录 cd astyle目录/build/gcc
    在这里插入图片描述
  4. 编译 make
    在这里插入图片描述
  5. 安装 make install
    在这里插入图片描述
  6. 测试 astyle -h 有说明内容,表示安装成功
    在这里插入图片描述

使用 astyle 工具:
astyle server2.cpp

[root@foundation1 C++7.18]# astyle server2.cpp 
Formatted  /root/Desktop/C++基础/C++7.18/server2.cpp

内容帮助你格式化
在这里插入图片描述
还可以用别的格式化风格
astyle --style=google server2.cpp 谷歌格式化风格
astyle --style=linux server2.cpp linux格式化风格
astyle --style=gnu server2.cpp gnu格式化风格

批量格式化:
astyle --style=google *.cpp 把当前所有cpp文件格式化

3.5 上传/下载文件

服务端:发送
直接传送文件函数 sendfile(传到哪儿, 文件, 偏移量, 文件长度);

#include <iostream>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <fcntl.h>
using namespace std;

int main(int argc,char** argv){
        if(4!=argc){                          // 终端输入文件名
                printf("argument error\n");
                printf("Usage:%s server_ip server_port work_path\n",argv[0]);
                return 1;
        }

        int fd = socket(AF_INET,SOCK_STREAM,0);
        if(-1 == fd){
                perror("open socket error");
                return 1;
        }

        sockaddr_in addr;
        addr.sin_family = AF_INET;
        inet_aton("127.0.0.1",&addr.sin_addr);
        addr.sin_port = htons(8080);
        int res = bind(fd,(sockaddr*)&addr,sizeof(addr));
        if(-1 == res){
                perror("bind error");
                return 1;
        }

        res = listen(fd,4);
        if(-1 == res){
                perror("listen error");
                return 1;
        }

        int connfd = accept(fd,NULL,NULL);
        if(-1 == connfd){
                perror("accept error");
                return 1;
        }


        char buffer[256] = {0};
        read(connfd,buffer,256);
        cout << buffer << endl;

        string file = string(argv[3])+"/"+buffer;
        printf("require %s\n",file.c_str());
        int filefd = open(file.c_str(),O_RDONLY);       // 打开文件,只读
        if(-1 == filefd){
                perror("open file error");
                return 1;
        }
        struct stat s;         // 结构体,st_size能够获取文件大小
        fstat(filefd,&s);
        sendfile(connfd,filefd,0,s.st_size);        // 可以直接发文件
        // write(connfd,s.c_str(),s.size()+1);

        close(connfd);
        close(fd);
}

客户端:下载

#include <iostream>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
using namespace std;

int main(int argc,char* argv[]){
        if(3!=argc){
                printf("argument error\n");
                printf("Usage:%s server_ip server_port\n",argv[0]);

        }
        int fd = socket(AF_INET,SOCK_STREAM,0);
        if(-1 == fd){
                perror("open socket error");
                return 1;
        }

        sockaddr_in addr;
        addr.sin_family = AF_INET;
        inet_aton("127.0.0.1",&addr.sin_addr);
        addr.sin_port = htons(8080);
        int res = connect(fd,(sockaddr*)&addr,sizeof(addr));
        if(-1 == res){
                perror("connect error");
                return 1;
        }

        string s;
        cin >> s;
        write(fd,s.c_str(),s.size()+1);
        string file = "./"+s;         // 当前路径下加文件名
        cout << file << endl;
        int filefd = open(file.c_str(),O_CREAT|O_WRONLY,0666);   // 创建一个文件
        if(-1 == filefd){
                perror("open file error");
                return 1;
        }

        for(;;){
                char buffer[256] = {0};
                int n = read(fd,buffer,256);
                write(filefd,buffer,n);            // 读到的内容写到刚创建的文件里面
                if(n < 256) break;
        }
        close(filefd);
        close(fd);
}

上传/下载步驟:

  1. 服务端终端输入要下载文件的路径,等待客户端下载
  2. 客户端与服务端连通后,输入要下载的文件名
  3. 客户端就能把文件下载到当前文件夹了,下载后两端都关闭
    在这里插入图片描述
    在这里插入图片描述

也可以在上传端输入文件目录的路径,但在下载端得先创建该目录,再下载,下载的路径才能找到

为了解决这个问题:
substr 切割字符
find_last_of('/') 查找 ’ / ’ 最后一次出现的位置
即使下载端输入的是目录和文件,也只会把文件下载到当前文件

        string s;
        cin >> s;
        write(fd,s.c_str(),s.size()+1);

        int pos = s.find_last_of('/');        // 找到最后文件名
        if(pos != string::npos){         // 如果找到了,进行切割
                s = substr(pos);
        }
        string file = "./"+s;        // 当前路径下加文件名
        cout << file << endl;
        int filefd = open(file.c_str(),O_CREAT|O_WRONLY,0666);   // 创建一个文件
        if(-1 == filefd){
                perror("open file error");
                return 1;
        }

3.6 对应目录下载

先服务器发给客户端有哪些文件,客户端再发送要下载的文件
使用管道在服务端执行命令ll -R(递归目录下的所有文件信息),把执行结果发送给客户端

管道读取 size_t fread ( 读到哪儿, 字节数, 大小, 从哪儿读)

服务端:上传

#include <iostream>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cstdio>
#include <thread>
using namespace std;

int main(int argc,char** argv){
        if(4!=argc){                          // 终端输入文件名
                printf("argument error\n");
                printf("Usage:%s server_ip server_port work_path\n",argv[0]);
                return 1;
        }

        int fd = socket(AF_INET,SOCK_STREAM,0);
        if(-1 == fd){
                perror("open socket error");
                return 1;
        }

        sockaddr_in addr;
        addr.sin_family = AF_INET;
        inet_aton("127.0.0.1",&addr.sin_addr);
        addr.sin_port = htons(8080);
        int res = bind(fd,(sockaddr*)&addr,sizeof(addr));
        if(-1 == res){
                perror("bind error");
                return 1;
        }

        res = listen(fd,4);
        if(-1 == res){
                perror("listen error");
                return 1;
        }

        for(;;){           // 多次接受
                int connfd = accept(fd,NULL,NULL);
                if(-1 == connfd){
                        perror("accept error");
                        return 1;
                }

                // 发送管道执行结果
                string cmd = string("ls -lR ")+argv[3];     // 获取命令信息,lR后面要加空格
                FILE* pfile = popen(cmd.c_str(),"r");
                if(NULL == pfile){
                        perror("popen error");
                        return 1;
                }
                for(;;){
                        char info[1024] = {0};
                        int n = fread(info,1,1024,pfile);      // 读取管道执行结果
                        write(connfd,info,n);            // 把结果写在连接中
                        if(n < 1024) break;
                }
                pclose(pfile);

                thread t( [=](){      // 按照值的方式=捕获
                        for(;;){
                                // 发送文件
                                char buffer[256] = {0};
                                int n = read(connfd,buffer,256);
                                if(n <= 0) break;
                                cout << buffer << endl;

                                string file = string(argv[3])+"/"+buffer;
                                printf("require %s\n",file.c_str());
                                int filefd = open(file.c_str(),O_RDONLY);       // 打开文件,只读
                                if(-1 == filefd){
                                        perror("open file error");
                                        return;
                                }
                                struct stat s;         // 结构体,st_size能够获取文件大小
                                fstat(filefd,&s);
                                sendfile(connfd,filefd,0,s.st_size);        // 可以直接发文件
                                // write(connfd,s.c_str(),s.size()+1);
                                close(filefd);
                        }
                        close(connfd);
                } );
                t.detach();
        }
        close(fd);
}

客户端:下载

#include <iostream>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
using namespace std;

int main(int argc,char* argv[]){
        if(3!=argc){
                printf("argument error\n");
                printf("Usage:%s server_ip server_port\n",argv[0]);

        }
        int fd = socket(AF_INET,SOCK_STREAM,0);
        if(-1 == fd){
                perror("open socket error");
                return 1;
        }

        sockaddr_in addr;
        addr.sin_family = AF_INET;
        inet_aton(argv[1],&addr.sin_addr);
        addr.sin_port = htons(atoi(argv[2]));
        int res = connect(fd,(sockaddr*)&addr,sizeof(addr));
        if(-1 == res){
                perror("connect error");
                return 1;
        }

        // 接收管道执行信息
        for(;;){
                char buffer[1025] = {0};           // 多申请一个/0
                int n = read(fd,buffer,1024);
                // write(STDOUT_FILENO,buffer,n);   // 无缓存的方式写,解决乱码问题
                cout << buffer;
                cout.flush();         // 手动刷数据
                if(n < 1024) break;
        }

        string s;
        while(cin >> s){       // 能够多次下载
                write(fd,s.c_str(),s.size()+1);

                int pos = s.find_last_of('/');        // 找到最后文件名
                if(pos != string::npos){         // 如果找到了,进行切割
                        s = s.substr(pos);
                }
                string file = "./"+s;        // 当前路径下加文件名
                cout << file << endl;
                int filefd = open(file.c_str(),O_CREAT|O_WRONLY,0666);   // 创建一个文件
                if(-1 == filefd){
                        perror("open file error");
                        return 1;
                }

                for(;;){
                        char buffer[256] = {0};
                        int n = read(fd,buffer,256);
                        write(filefd,buffer,n);            // 读到的内容写到刚创建的文件里面
                        if(n < 256) break;
                }
                close(filefd);
        }
        close(fd);
}

结果为:

  1. 先打开服务端
    在这里插入图片描述
  2. 再开服务端输入要下载的文件
    在这里插入图片描述
  3. 在当前目录经能看到这个文件,可以重复下载
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值