从零开始学习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);
}

教程非常不错,价值280元,绝对是干货 Linux网络编程(总共41集) 讲解Linux网络编程知识,分以下四个篇章。 Linux网络编程之TCP/IP基础篇 Linux网络编程之socket编程篇 Linux网络编程之进程间通信篇 Linux网络编程之线程篇 Linux网络编程之TCP/IP基础篇 01TCPIP基础(一) ISO/OSI参考模型 TCP/IP四层模型 基本概念(对等通信、封装、分用、端口) 02TCPIP基础(二) 最大传输单元(MTU)/路径MTU 以太网帧格式 ICMP ARP RARP 03TCPIP基础(三) IP数据报格式 网际校验和 路由 04TCPIP基础(四) TCP特点 TCP报文格式 连接建立三次握手 连接终止四次握手 TCP如何保证可靠性 05TCPIP基础(五) 滑动窗口协议 UDP特点 UDP报文格式 Linux网络编程之socket编程篇 06socket编程(一) 什么是socket IPv4套接口地址结构 网络字节序 字节序转换函数 地址转换函数 套接字类型 07socket编程(二) TCP客户/服务器模型 回射客户/服务器 socket、bind、listen、accept、connect 08socket编程(三) SO_REUSEADDR 处理多客户连接(process-per-conection) 点对点聊天程序实现 09socket编程(四) 流协议与粘包 粘包产生的原因 粘包处理方案 readn writen 回射客户/服务器 10socket编程(五) read、write与recv、send readline实现 用readline实现回射客户/服务器 getsockname、getpeername gethostname、gethostbyname、gethostbyaddr 11socket编程(六) TCP回射客户/服务器 TCP是个流协议 僵进程与SIGCHLD信号 12socket编程(七) TCP 11种状态 连接建立三次握手、连接终止四次握手 TIME_WAIT与SO_REUSEADDR SIGPIPE 13socket编程(八) 五种I/O模型 select 用select改进回射客户端程序 14socket编程(九) select 读、写、异常事件发生条件 用select改进回射服务器程序。 15socket编程(十) 用select改进第八章点对点聊天程序 16socket编程(十一) 套接字I/O超时设置方法 用select实现超时 read_timeout函数封装 write_timeout函数封装 accept_timeout函数封装 connect_timeout函数封装 17socket编程(十二) select限制 poll 18socket编程(十三) epoll使用 epoll与select、poll区别 epoll LT/ET模式 19socket编程(十四) UDP特点 UDP客户/服务基本模型 UDP回射客户/服务器 UDP注意点 20socket编程(十五) udp聊天室实现 21socket编程(十六) UNIX域协议特点 UNIX域地址结构 UNIX域字节流回射客户/服务 UNIX域套接字编程注意点 22socket编程(十七) socketpair sendmsg/recvmsg UNIX域套接字传递描述符字 Linux网络编程之进程间通信篇 23进程间通信介绍(一) 进程同步与进程互斥 进程间通信目的 进程间通信发展 进程间通信分类 进程间共享信息的三种方式 IPC对象的持续性 24进程间通信介绍(二) 死锁 信号量 PV原语 用PV原语解决司机与售票员问题 用PV原语解决民航售票问题 用PV原语解决汽车租赁问题 25System V消息队列(一) 消息队列 IPC对象数据结构 消息队列结构 消息队列在内核中的表示 消息队列函数 26System V消息队列(二) msgsnd函数 msgrcv函数 27System V消息队列(三) 消息队列实现回射客户/服务器 28共享内存介绍 共享内存 共享内存示意图 管道、消息队列与共享内存传递数据对比 mmap函数 munmap函数 msync函数 29System V共享内存 共享内存数据结构 共享内存函数 共享内存示例 30System V信号量(一) 信号量 信号量集结构 信号量集函数 信号量示例 31System V信号量(二) 用信号量实现进程互斥示例 32System V信号量(三) 用信号集解决哲学家就餐问题 33System V共享内存与信号量综合 用信号量解决生产者消费者问题 实现shmfifo 34POSIX消息队列 POSIX消息队列相关函数 POSIX消息队列示例 35POSIX共享内存 POSIX共享内存相关函数 POSIX共享内存示例 Linux网络编程之线程篇 36线程介绍 什么是线程 进程与线程 线程优缺点 线程模型 N:1用户线程模型 1:1核心线程模型 N:M混合线程模型 37POSIX线程(一) POSIX线程库相关函数 用线程实现回射客户/服务器 38POSIX线程(二) 线程属性 线程特定数据 39POSIX信号量与互斥锁 POSIX信号量相关函数 POSIX互斥锁相关函数 生产者消费者问题 自旋锁与读写锁介绍 40POSIX条件变量 条件变量 条件变量函数 条件变量使用规范 使用条件变量解决生产者消费者问题 41一个简单的线程池实现 线程池性能分析 线程池实现 网络编程, Linux, 密码
Linux网络编程(总共41集) 讲解Linux网络编程知识,分以下四个篇章。 Linux网络编程之TCP/IP基础篇 Linux网络编程之socket编程篇 Linux网络编程之进程间通信篇 Linux网络编程之线程篇 Linux网络编程之TCP/IP基础篇 01TCPIP基础(一) ISO/OSI参考模型 TCP/IP四层模型 基本概念(对等通信、封装、分用、端口) 02TCPIP基础(二) 最大传输单元(MTU)/路径MTU 以太网帧格式 ICMP ARP RARP 03TCPIP基础(三) IP数据报格式 网际校验和 路由 04TCPIP基础(四) TCP特点 TCP报文格式 连接建立三次握手 连接终止四次握手 TCP如何保证可靠性 05TCPIP基础(五) 滑动窗口协议 UDP特点 UDP报文格式 Linux网络编程之socket编程篇 06socket编程(一) 什么是socket IPv4套接口地址结构 网络字节序 字节序转换函数 地址转换函数 套接字类型 07socket编程(二) TCP客户/服务器模型 回射客户/服务器 socket、bind、listen、accept、connect 08socket编程(三) SO_REUSEADDR 处理多客户连接(process-per-conection) 点对点聊天程序实现 09socket编程(四) 流协议与粘包 粘包产生的原因 粘包处理方案 readn writen 回射客户/服务器 10socket编程(五) read、write与recv、send readline实现 用readline实现回射客户/服务器 getsockname、getpeername gethostname、gethostbyname、gethostbyaddr 11socket编程(六) TCP回射客户/服务器 TCP是个流协议 僵进程与SIGCHLD信号 12socket编程(七) TCP 11种状态 连接建立三次握手、连接终止四次握手 TIME_WAIT与SO_REUSEADDR SIGPIPE 13socket编程(八) 五种I/O模型 select 用select改进回射客户端程序 14socket编程(九) select 读、写、异常事件发生条件 用select改进回射服务器程序。 15socket编程(十) 用select改进第八章点对点聊天程序 16socket编程(十一) 套接字I/O超时设置方法 用select实现超时 read_timeout函数封装 write_timeout函数封装 accept_timeout函数封装 connect_timeout函数封装 17socket编程(十二) select限制 poll 18socket编程(十三) epoll使用 epoll与select、poll区别 epoll LT/ET模式 19socket编程(十四) UDP特点 UDP客户/服务基本模型 UDP回射客户/服务器 UDP注意点 20socket编程(十五) udp聊天室实现 21socket编程(十六) UNIX域协议特点 UNIX域地址结构 UNIX域字节流回射客户/服务 UNIX域套接字编程注意点 22socket编程(十七) socketpair sendmsg/recvmsg UNIX域套接字传递描述符字 Linux网络编程之进程间通信篇 23进程间通信介绍(一) 进程同步与进程互斥 进程间通信目的 进程间通信发展 进程间通信分类 进程间共享信息的三种方式 IPC对象的持续性 24进程间通信介绍(二) 死锁 信号量 PV原语 用PV原语解决司机与售票员问题 用PV原语解决民航售票问题 用PV原语解决汽车租赁问题 25System V消息队列(一) 消息队列 IPC对象数据结构 消息队列结构 消息队列在内核中的表示 消息队列函数 26System V消息队列(二) msgsnd函数 msgrcv函数 27System V消息队列(三) 消息队列实现回射客户/服务器 28共享内存介绍 共享内存 共享内存示意图 管道、消息队列与共享内存传递数据对比 mmap函数 munmap函数 msync函数 29System V共享内存 共享内存数据结构 共享内存函数 共享内存示例 30System V信号量(一) 信号量 信号量集结构 信号量集函数 信号量示例 31System V信号量(二) 用信号量实现进程互斥示例 32System V信号量(三) 用信号集解决哲学家就餐问题 33System V共享内存与信号量综合 用信号量解决生产者消费者问题 实现shmfifo 34POSIX消息队列 POSIX消息队列相关函数 POSIX消息队列示例 35PO
Linux网络编程(总共41集) 讲解Linux网络编程知识,分以下四个篇章。 Linux网络编程之TCP/IP基础篇 Linux网络编程之socket编程篇 Linux网络编程之进程间通信篇 Linux网络编程之线程篇 Linux网络编程之TCP/IP基础篇 01TCPIP基础(一) ISO/OSI参考模型 TCP/IP四层模型 基本概念(对等通信、封装、分用、端口) 02TCPIP基础(二) 最大传输单元(MTU)/路径MTU 以太网帧格式 ICMP ARP RARP 03TCPIP基础(三) IP数据报格式 网际校验和 路由 04TCPIP基础(四) TCP特点 TCP报文格式 连接建立三次握手 连接终止四次握手 TCP如何保证可靠性 05TCPIP基础(五) 滑动窗口协议 UDP特点 UDP报文格式 Linux网络编程之socket编程篇 06socket编程(一) 什么是socket IPv4套接口地址结构 网络字节序 字节序转换函数 地址转换函数 套接字类型 07socket编程(二) TCP客户/服务器模型 回射客户 /服务器 socket、bind、listen、accept、connect 08socket编程(三) SO_REUSEADDR 处理多客户连接(process-per-conection) 点对点聊天程序实现 09socket编程(四) 流协议与粘包 粘包产生的原因 粘包处理方案 readn writen 回射客户/服务器 10socket编程(五) read、write与recv、send readline实现 用readline实现回射客户/服务器 getsockname、getpeername gethostname、gethostbyname、gethostbyaddr 11socket编程(六) TCP回射客户/服务器 TCP是个流协议 僵进程与SIGCHLD信号 12socket编程(七) TCP 11种状态 连接建立三次握手、连接终止四次握手 TIME_WAIT与SO_REUSEADDR SIGPIPE 13socket编程(八) 五种I/O模型 select 用select改进回射客户端程序 14socket编程(九) select 读、写、异常事件发生条件 用select改进回射服务器程序。 15socket编程(十) 用select改进第八章点对点聊天程序 16socket编程(十一) 套接字I/O超时设置方法 用select实现超时 read_timeout函数封装 write_timeout函数封装 accept_timeout函数封装 connect_timeout函数封装 17socket编程(十二) select限制 poll 18socket编程(十三) epoll使用 epoll与select、poll区别 epoll LT/ET模式 19socket编程(十四) UDP特点 UDP客户/服务基本模型 UDP回射客户/服务器 UDP注意点 20socket编程(十五) udp聊天室实现 21socket编程(十六) UNIX域协议特点 UNIX域地址结构 UNIX域字节流回射客户/服务 UNIX域套接字编程注意点 22socket编程(十七) socketpair sendmsg/recvmsg UNIX域套接字传递描述符字 Linux网络编程之进程间通信篇 23进程间通信介绍(一) 进程同步与进程互斥 进程间通信目的 进程间通信发展 进程间通信分类 进程间共享信息的三种方式 IPC对象的持续性 24进程间通信介绍(二) 死锁 信号量 PV原语 用PV原语解决司机与售票员问题 用PV原语解决民航售票问题 用PV原语解决汽车租赁问题 25System V消息队列(一) 消息队列 IPC对象数据结构 消息队列结构 消息队列在内核中的表示 消息队列函数 26System V消息队列(二) msgsnd函数 msgrcv函数 27System V消息队列(三) 消息队列实现回射客户/服务器 28共享内存介绍 共享内存 共享内存示意图 管道、消息队列与共享内存传递数据对比 mmap函数 munmap函数 msync函数 29System V共享内存 共享内存数据结构 共享内存函数 共享内存示例 30System V信号量(一) 信号量 信号量集结构 信号量集函数 信号量示例 31System V信号量(二) 用信号量实现进程互斥示例 32System V信号量(三) 用信号集解决哲学家就餐问题 33System V共享内存与信号量综合 用信号量解决生产者消费者问题 实现shmfifo 34POSIX消息队列 POSIX消息队列相关函数 POSIX消息队列示例 35POSIX共享内存 POSIX共享内存相关函数 POSIX共享内存示例 Linux网络编程之线程篇 36线程介绍 什么是线程 进程与线程 线程优缺点 线程模型 N:1用户线程模型 1:1核心线程模型 N:M混合线程模型 37POSIX线程(一) POSIX线程库相关函数 用线程实现回射客户/服务器 38POSIX线程(二) 线程属性 线程特定数据 39POSIX信号量与互斥锁 POSIX信号量相关函数 POSIX互斥锁相关函数 生产者消费者问题 自旋锁与读写锁介绍 40POSIX条件变量 条件变量 条件变量函数 条件变量使用规范 使用条件变量解决生产者消费者问题 41一个简单的线程池实现 线程池性能分析 线程池实现 网络编程, Linux
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值