C++基础--建立socket连接

在这里我建立一个最基本的TCP/IP连接,只传输最基础的数据,不考虑复用IO。


客户端

首先,命令行的参数是这样的

./client ip 5005

同时在文件内也要限定命令行参数

    if(argc!=3)

    {

        cout<<"Using:  ./client ip 5005\n";
        return 0;

    }

在这里IP就是你要连接的服务器IP地址

前置条件已说完,可以开始了

第一步

通过socket函数创建socket

    int sockfd =socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    if(sockfd==-1)
    {
        perror("socket");return -1;
    }

我以前写过关于socket函数各个参数的介绍

复习socket()函数 - 留名有道 - 博客园

第二步

向服务器发起连接请求

我们需要服务器的协议族、端口、IP来作为连接请求的参数。幸好有结构体可以帮忙。

struct sockaddr_in {
    short int sin_family;      // 协议族,通常为 AF_INET
    unsigned short int sin_port; // 端口号(网络字节顺序)
    struct in_addr sin_addr;   // IP地址
    unsigned char sin_zero[8]; // 填充字段,使结构体大小与 sockaddr 一致
};

填充字段没有内容,那是为了今后扩充写的,可是现在的ipv6好像也没用上哈哈。

第一个字段就填服务端的协议族,这里我是AF_INET

sin_family =AF_INET;

第二个字段填写端口号,端口号需要是大端序,很多系统是小端序,所以需要转换

不懂的可以在百度上学习查找,几分钟就可以了。

转换的函数是htons(),这是原型

uint16_t htons(uint16_t hostshort);

这里我们用命令行的参数就行了

sin_port = htons(atoi(argv[2]));

补充一下atoi()是处理C风格的字符串 ,stoi()是处理C++风格的

第三个字段如果你有IP地址的话使用htonl() 函数就行了,如果是域名就需要使用gethostbyname() 函数

如果使用htonl()函数只需要这样写

sin_addr.s_addr=htonl(argv[1);

如果使用gethostbyname()函数就需要结构体来保存从域名转换来的IP地址

hostent *gethostbyname(const char *name)

函数返回的是一个结构体

struct hostent {
    char  *h_name;        // 官方主机名
    char **h_aliases;     // 主机的别名列表
    int    h_addrtype;    // 地址类型(例如,AF_INET)
    int    h_length;      // 地址长度(例如,IPv4的长度为4)
    char **h_addr_list;   // 网络地址列表(每个地址的长度由h_length确定)
};

这里我们得到结构体后复制到sockaddr_in 也就是连接的结构体中

struct hostent* h;                         //存放服务器端IP的结构体
if((h=gethostbyname(argv[1]))==nullptr)    //将域名解析为IP地址
{
    cout<<"gethostbyname failed()\n";
    close(sockfd);
    return -1;
}
memcpy(&cliaddr.sin_addr,h->h_addr,h->h_length);

第四步 就是连接了,连接需要使用函数connect()

  1. int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen)

连接时需要的结构体与我们连接的结构体不同,需要强制类型转换,函数的第三个参数是确定第二个参数的大小

if(connect(sockfd,(struct sockaddr *)&cliaddr,sizeof(cliaddr))==-1)      //发起连接请求
{
    perror("connect");close(sockfd);return -1;
}

第三步

使用send()和recv()函数传送和接收数据

这里去连接自己的服务器端

    while(true)
    {
        memset(buffer,0,sizeof(buffer));
        snprintf(buffer,1024,"这是第%d个消息.\n",tmp++);
        if(send(sockfd,buffer,sizeof(buffer),0)<=0)
        {
            perror("send");close(sockfd);return -1;
        }
        memset(buffer,0,sizeof(buffer));
        if((iret=recv(sockfd,buffer,sizeof(buffer),0))<=0)
        {
            cout<<"iret="<<iret<<endl;
        }
        cout<<"接收"<<buffer<<endl;
        sleep(2);
    }

服务端

服务端在命令行我只指定了打开的端口号,客户端连接执行需要访问这个端口,然后自动分配在其他端口传送和接收数据

    if(argc!=2)
    {
        cout<<"Using: ./server 5005\n";
        return -1;
    }

第一步

这里只需要获得一个socket

    int listenfd =socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    if(listenfd==-1)
    {
        perror("socket");return -1;
    }

第二步

我们需要确定连接来的客户端IP等,与客户端的第二步很相像

struct sockaddr_in servaddr;
    servaddr.sin_family=AF_INET;
    servaddr.sin_port=htons(atoi(argv[1]));
    servaddr.sin_addr.s_addr=htonl(INADDR_ANY);        //将32位主机字节顺序(Host Byte Order)的整数转换为网络字节顺序

这里说一下INADDR_ANY宏是 0.0.0.0的意思,代表接收所有IP地址的连接

第三步

绑定服务端的IP和端口

    if(bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr))==-1)
    {
        perror("bind");close(listenfd);return -1;
    }

第四步

将socket设置为监听状态

    if(listen(listenfd,5)==-1)                  //5表示挂起连接请求的最大队列长度
    {
        perror("listen");close(listenfd);return -1;
    }

通过listen()函数将socket设置为监听状态

int listen(int sockfd, int backlog);

其中的backlog表示挂起连接请求的最大队列数

第五步

受理连接

    int sockfd=accept(listenfd,0,0);
    if(sockfd==-1)
    {
        perror("accept");close(listenfd);return -1;
    }

accept()的函数原型

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

其中的第二个参数用于存储客户端的地址信息,不需要存储可以填NULL,第三个参数确定第二个参数的长度

第六步

建立一个循环传送和传输内容

    while(true)
    {
        memset(buffer,0,sizeof(buffer));
        if((iret=recv(sockfd,buffer,sizeof(buffer),0))<=0)
        {
            cout<<"iret="<<iret<<endl;close(listenfd);return -1;
        }
        cout<<"接收="<<buffer<<endl;

        strcpy(buffer,"ok!\n");
        if(iret=send(sockfd,buffer,sizeof(buffer),0)<=0)
        {
            cout<<"iret="<<iret<<endl;close(listenfd);return -1;
        }
    }

附上源码

客户端

#include<stdio.h>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<unistd.h>
using namespace std;
int main (int argc,char *argv[])
{
    if(argc!=3)
    {
        cout<<"Using:  ./client ip 5005\n";
        return 0;
    }

    //第一步,创建客户端的socket
    int sockfd =socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    if(sockfd==-1)
    {
        perror("socket");return -1;
    }

    //第二步,向服务器发起连接请求
    // struct sockaddr_in {
    //     short int sin_family;      // 协议族,通常为 AF_INET
    //     unsigned short int sin_port; // 端口号(网络字节顺序)
    //     struct in_addr sin_addr;   // IP地址
    //     unsigned char sin_zero[8]; // 填充字段,使结构体大小与 sockaddr 一致
    // };
    struct sockaddr_in cliaddr;               //存放协议、端口和IP地址的结构体
    memset(&cliaddr,0,sizeof(cliaddr));
    cliaddr.sin_family =AF_INET;
    cliaddr.sin_port = htons(atoi(argv[2]));         //16位主机字节顺序(Host Byte Order)的整数转换为网络字节顺序
    // struct hostent {
    //     char  *h_name;        // 官方主机名
    //     char **h_aliases;     // 主机的别名列表
    //     int    h_addrtype;    // 地址类型(例如,AF_INET)
    //     int    h_length;      // 地址长度(例如,IPv4的长度为4)
    //     char **h_addr_list;   // 网络地址列表(每个地址的长度由h_length确定)
    // };
    //其中有宏定义为 #define h_addr h_addr_list[0]
    struct hostent* h;                         //存放服务器端IP的结构体
    if((h=gethostbyname(argv[1]))==nullptr)    //将域名解析为IP地址
    {
        cout<<"gethostbyname failed()\n";
        close(sockfd);
        return -1;
    }
    memcpy(&cliaddr.sin_addr,h->h_addr,h->h_length);
    if(connect(sockfd,(struct sockaddr *)&cliaddr,sizeof(cliaddr))==-1)      //发起连接请求
    {
        perror("connect");close(sockfd);return -1;
    }

    char buffer[1024];
    int tmp = 1;
    int iret;
    while(true)
    {
        memset(buffer,0,sizeof(buffer));
        snprintf(buffer,1024,"这是第%d个消息.\n",tmp++);
        if(send(sockfd,buffer,sizeof(buffer),0)<=0)
        {
            perror("send");close(sockfd);return -1;
        }
        memset(buffer,0,sizeof(buffer));
        if((iret=recv(sockfd,buffer,sizeof(buffer),0))<=0)
        {
            cout<<"iret="<<iret<<endl;
        }
        cout<<"接收"<<buffer<<endl;
        sleep(2);
    }
    close(sockfd);
    return 0;
}

服务端

//typedef unsigned int in_addr_t    存放32位大端序的IP地址
//把字符串格式的IP转换成大端序的IP,转换后的IP直接填充到sockaddr_in.in_addr.s_addr
//in_addr_t inet_addr(const char *cp);
//把字符串格式的IP转换成大端序的IP,转化后的IP直接填充到sockaddr_in.in_addr成员
//int inet_aton(const char * cp,struct in_addr *inp)
//把大端序的IP转化成字符串形式的IP,用于在服务端程序中解析客户端的IP地址
//char *inet_nota(struct in_addr in)
#include<stdio.h>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<unistd.h>
#include<netdb.h>
using namespace std;
int main(int argc,char *argv[])
{
    if(argc!=2)
    {
        cout<<"Using: ./server 5005\n";
        return -1;
    }

    //第一步,创建socket
    int listenfd =socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    if(listenfd==-1)
    {
        perror("socket");return -1;
    }

    //第二步,把服务端用于通信的IP和端口绑定到socket上
        // struct sockaddr_in {
    //     short int sin_family;      // 协议族,通常为 AF_INET
    //     unsigned short int sin_port; // 端口号(网络字节顺序)
    //     struct in_addr sin_addr;   // IP地址
    //     unsigned char sin_zero[8]; // 填充字段,使结构体大小与 sockaddr 一致
    // };
    struct sockaddr_in servaddr;
    servaddr.sin_family=AF_INET;
    servaddr.sin_port=htons(atoi(argv[1]));
    servaddr.sin_addr.s_addr=htonl(INADDR_ANY);        //将32位主机字节顺序(Host Byte Order)的整数转换为网络字节顺序
    //第三步,绑定服务端的IP和端口
    if(bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr))==-1)
    {
        perror("bind");close(listenfd);return -1;
    }

    //第四步,把socket设置为可监听的状态
    if(listen(listenfd,5)==-1)                  //5表示挂起连接请求的最大队列长度
    {
        perror("listen");close(listenfd);return -1;
    }

    //第五步,受理连接请求,accept为阻塞等待
    int sockfd=accept(listenfd,0,0);
    if(sockfd==-1)
    {
        perror("accept");close(listenfd);return -1;
    }

    char buffer[1024];
    int iret;
    while(true)
    {
        memset(buffer,0,sizeof(buffer));
        if((iret=recv(sockfd,buffer,sizeof(buffer),0))<=0)
        {
            cout<<"iret="<<iret<<endl;close(listenfd);return -1;
        }
        cout<<"接收="<<buffer<<endl;

        strcpy(buffer,"ok!\n");
        if(iret=send(sockfd,buffer,sizeof(buffer),0)<=0)
        {
            cout<<"iret="<<iret<<endl;close(listenfd);return -1;
        }
    }


    return 0;
}
posted @ 2025-02-28 09:54  留名有道  阅读(289)  评论(0)    收藏  举报