从零开始学习Linux网络编程C++ Day1

        最近找实习了,发现很多岗位需要网络编程,但是在计算机网络课上又没有好好听,所以最近又开始重新学习了一下,一切从实践出发,帮助快速上手网络编程。

搭建服务端。

1. 创建服务端 Socket

int serveSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  • AF_INET :指定使用 IPv4 地址族,你也可以使用IPv6(AF_INET6)。

  • SOCK_STREAM :表示使用面向连接的流式传输协议(TCP)。

  • IPPROOT_TCP :明确使用 TCP 协议。

如果创建成功,socket 函数会返回一个套接字描述符(serveSocket),用于后续的操作;如果失败,则返回负值,并设置相应的错误码。 

2. 绑定 Socket

struct sockaddr_in sockaddr;
memset(&sockaddr, 0, sizeof(sockaddr)); // 初始化为 0
sockaddr.sin_family = AF_INET; // 设置地址簇
sockaddr.sin_addr.s_addr = inet_addr(ip.c_str()); // 设置 IP 地址
sockaddr.sin_port = htons(port); // 设置端口号,使用网络字节序
  • sockaddr_in 是一种标准的结构体类型,用于表示 IPv4 地址和端口号信息。

  • inet_addr 函数将点分十进制的 IP 地址字符串转换为网络字节序的 32 位无符号整数。

  • htons 函数将主机字节序的短整型数值转换为网络字节序。

绑定操作是将套接字与特定的 IP 地址和端口号关联起来,以便客户端能够通过该地址和端口与服务器进行通信。如果绑定失败,可能是由于端口已被占用、IP 地址不正确等原因。

3.监听 Socket

if (listen(serveSocket, 1024) < 0) {
    cout << "listen failed" << endl;
    return -1;
} else {
    cout << "socket listen..." << endl;
}
  • listen 函数将已绑定的套接字转换为被动套接字,使其可以接受传入的连接请求。

  • 第二个参数 1024 表示监听队列的最大长度,即操作系统可以同时保存多少个未处理的连接请求。

当服务器开始监听后,就可以等待客户端的连接了。

4.接受客户端连接

int connfd = accept(serveSocket, nullptr, nullptr);
  • accept 函数从监听队列中取出一个连接请求,并返回一个新的套接字描述符(connfd),用于与该客户端进行通信。

  • 如果没有客户端连接,accept 函数会阻塞,直到有客户端连接到来。

5.接收和发送数据 

size_t len = recv(connfd, buf, sizeof(buf), 0);
send(connfd, buf, sizeof(buf), 0);
  • recv 函数用于从已连接的套接字中接收数据,将接收到的数据存储到缓冲区 buf 中,并返回接收到的字节数。如果对端关闭了连接,返回值为 0。

  • send 函数用于向已连接的套接字发送数据,从缓冲区 buf 中读取数据并发送出去。

  • buf的定义为char buf[1024]。

 通过接收和发送数据,服务器和客户端之间就可以实现双向通信了。

6.关闭 Socket

close(serveSocket);

在完成通信后,需要关闭套接字以释放系统资源。如果服务器需要持续运行并接受多个客户端连接,可以将相关代码放入循环中,并在适当的时候关闭套接字。

搭建客户端

1. 创建客户端 Socket

int clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

客户端创建 Socket 的方式与服务器类似,也是指定地址族、套接字类型和协议。

2. 连接服务端

struct sockaddr_in sockaddr;
memset(&sockaddr, 0, sizeof(sockaddr));
sockaddr.sin_family = AF_INET;
sockaddr.sin_addr.s_addr = inet_addr(ip.c_str());
sockaddr.sin_port = htons(port);

if (connect(clientSocket, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) < 0) {
    cout << "connect failed" << endl;
    return -1;
} else {
    cout << "connect success" << endl;
}
  • 客户端通过 connect 函数主动连接服务器,指定服务器的 IP 地址和端口号。

  • 如果连接成功,就可以与服务器进行通信了;如果连接失败,可能是由于服务器未启动、IP 地址或端口号错误等原因。

3. 发送和接收数据 

string result = to_string(c) + data;
if (send(clientSocket, result.c_str(), result.size(), 0) < 0) {
    cout << "error" << endl;
    return -1;
}

memset(buf, 0, sizeof(buf));
recv(clientSocket, buf, sizeof(buf), 0);
cout << buf << endl;
  • 客户端通过 send 函数向服务器发送数据,将数据拼接成字符串后发送出去。

  • 通过 recv 函数接收服务器返回的数据,并将其打印出来。 

4.关闭 Socket 

close(clientSocket);

在完成通信后,客户端也需要关闭套接字。

你还可以在客户端增加接收服务端的消息,在服务端增加发送消息的代码,也就是用几次send函数,我在上面也就没多写了,在下面的完整代码中有。

完整代码

服务端

#include <iostream>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>//inet_addr()
#include <netinet/in.h>//有IPPROTO_TCP
#include <unistd.h>//close
#include <cstring>

using namespace std;

int main(){
    //第一步,创建服务端socket
    int serveSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//SOCK_STREAM是TCP,SOCK_DGRAM是UDP
    if (serveSocket < 0) {
        cout << "Socket creation failed" << endl;
        return -1;
    }else{
        cout<<"create socket success"<<endl;
    }
    //第二步,绑定socket
    string ip="192.168.66.132";//自己服务端的ip
    int port=50000;//自己服务端的端口号

    struct sockaddr_in sockaddr;//sockaddr_in 是一种标准的结构体类型,用于表示 IPv4 地址和端口号信息
    memset(&sockaddr,0,sizeof(sockaddr));//初始化为0
    sockaddr.sin_family=AF_INET;//设置地址簇
    sockaddr.sin_addr.s_addr=inet_addr(ip.c_str());
    sockaddr.sin_port=htons(port);
    if(bind(serveSocket,(struct sockaddr *)&sockaddr,sizeof(sockaddr))<0){//将一个套接字(socket)绑定到指定的本地地址和端口。
        cout<<"bind failed"<<endl;
        return -1;
    }else{
        cout<<"bind success"<<endl;
    }

    //第三步,监听socket
    //listen() 是一个系统调用,用于将一个已绑定的套接字(通过 bind())转换为被动套接字(即监听套接字),以便它可以接受传入的连接请求。
    //1024 是传递给 listen() 的第二个参数 backlog,表示监听队列的最大长度。这决定了操作系统可以同时保存多少个未处理的连接请求。
    if(listen(serveSocket,1024)<0){
        cout<<"listen failed"<<endl;
        return -1;
    }else{
        cout<<"socket listen..."<<endl;
    }

    char buf[1024]={0};//用于接受客户端发送过来的信息
    while(true){
        //第四步,接受客户端连接
        int connfd=accept(serveSocket,nullptr,nullptr);//调用 accept() 函数从 serveSocket 的监听队列中取出一个连接请求,并返回一个新的套接字描述符 connfd
        //connfd 是专门为该客户端创建的套接字描述符,包含与客户端通信所需的所有必要信息。
        if(connfd<0){
            cout<<"accept socket failed"<<endl;
            return -1;
        }

        while(true){
            memset(buf, 0, sizeof(buf)); // 清空缓冲区

            //第五步,接受客户端的数据
            size_t len=recv(connfd,buf,sizeof(buf),0);//通过 recv() 函数从已连接的套接字 connfd 中接收数据,并将其存储到缓冲区 buf 中
            //成功时返回接收到的字节数。如果对端关闭了连接,返回值为 0
            cout<<"connfd:"<<connfd<<"   "<<"buf:"<<buf<<endl;
    
            
            // 根据 buf[0] 的值进行分支判断
            string response;
            switch(buf[0]){
                case '1': response = "Response for option 1"; break;
                case '2': response = "Response for option 2"; break;
                case '3': response = "Response for option 3"; break;
                case '0': 
                    response = "Goodbye";
                    send(connfd,response.c_str(), response.length(),0);
                    close(connfd);
                    goto break_label;
                    break;
                default: response = "Invalid option";
            }
    
            
            //第六步,向客户端发送数据
            send(connfd,response.c_str(),response.length(),0);

            break_label:
            break;
            
        }
        
    }
    //第七步,关闭socket
    close(serveSocket);
    return 0;
}

客户端

#include <iostream>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>//有IPPROTO_TCP
#include <unistd.h>//close
#include <cstring>

using namespace std;

int main(){
    //第一步,创建socket
    int clientSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    if(clientSocket<0){
        cout<<"socket create failed"<<endl;
        return -1;
    }else{
        cout<<"socket create success"<<endl;
    }

    //第二步,连接服务端
    string ip="192.168.66.132";//服务端的ip
    int port=50000;//服务端的端口

    struct sockaddr_in sockaddr;
    memset(&sockaddr,0,sizeof(sockaddr));
    sockaddr.sin_family=AF_INET;
    sockaddr.sin_addr.s_addr=inet_addr(ip.c_str());//inet_addr 的作用:将点分十进制的 IPv4 地址字符串转换为网络字节序的 32 位无符号整数
    sockaddr.sin_port=htons(port);//htons(port) 是一个用于将主机字节序(Host Byte Order)的短整型数值转换为网络字节序(Network Byte Order)

    if(connect(clientSocket,(struct sockaddr *)&sockaddr,sizeof(sockaddr))<0){
        cout<<"connect failed"<<endl;
        return -1;
    }else{
        cout<<"connect success"<<endl;
    }

    string data;
    int c;
    char buf[1024]={0};
    //第三步,向服务端发送数据
    while(true){
        cout<<"请选择:1.... 2.... 3.... 0.退出"<<endl;
        cin>>c;
        cin.ignore(); // 清除缓冲区中的换行符
        cout<<"请输入要传送的内容"<<endl;
        getline(cin, data);
        string result=to_string(c)+data;
        data.clear();
        if(send(clientSocket,result.c_str(),result.size(),0)<0){
            cout<<"error"<<endl;
            return -1;
        }
        
        if(c==0){
            break;
        }
        
        //第四步,接受服务端消息
        memset(buf, 0, sizeof(buf)); // 清空缓冲区
        recv(clientSocket,buf,sizeof(buf),0);
        cout<<buf<<endl;
    }
    
    //第五步,关闭socket
    close(clientSocket);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值