【操作系统---20】套接字编程

基于udp协议的socket客户端和服务端的编程(接口):

    int socket(int domain, int type, int protocol);//创建套接字

    int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);//为套接字绑定地址信息

    ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);

    ssize_t sendto(int sockfd, const void *buf, size_t len,int flags,const struct sockaddr *dest_addr,
    
    socklen_taddrlen);

    int close(int fd);//关闭套接字

客户端:

<1>.创建套接字,使进程和网卡之间建立关联

<2>.为套接字绑定地址信息,发送数据的时候能够表述数据从哪个地址端口发送出去,对方回复数据就会恢复到这个地址端口上

<3>.发送数据,将data中dlen长度的数据通过socketfd对应的socket结构体中的ip/端口将数据发送到dest_addr地址的主机上

    buf:要发送的数据

    len:发送的数据的长度

    flag:标志,0表示默认阻塞

    const struct sockaddr *dest_addr:对端地址信息

    socklen_t addrlen:对端地址长度

<4>.接收数据,从socketfd对应的socket结构体的接收队列中取出一条数据放到buf中

服务端:

<1>.创建套接字,domain:地址域,AF_INET(ipv4)地址域

    type:套接字类型  SOC_STREAM流式套接字  SOC_DGRAM数据报套接字

    proto:传输层协议类型  0:默认 IPPROTO_TCP 6   IPPROTO_UDP 17

    返回值:套接字操作句柄---文件描述符

<2>.为套接字绑定地址信息,int bind(int sockfd, const struct sockaddr * addr,socklen_t addrlen);

    sockfd:创建好的套接字操作句柄

    struct sockaddr:用同一的结构体,用户自己传入sa_family,16位地址类型,从而决定使用哪种解析方式,实现统一管理

    在socket结构体中描述符这个socket处理哪个地址和端口的数据

    网卡把数据发送到socket结构体中的接收队列

服务端绑定地址的目的:告诉操作系统网卡接收到数据的时候,哪个地址和端口的数据应该放到哪个socket结构体中的接收队列

注意事项:

    htons,主机字节序转换为网络字节序 uint16_t htons(uint16_t hostshort);

    inet_addr,把点分十进制的字符串IP地址,转换为网络字节序IP地址 in_addr_t inet_addr(const char *cp);

    struct sockaddr_in addr;

    addr.sin_family=AF_INET;

    addr.sin_port=htons(peer_port);

    addr.sin_addr.s_addr=inet_addr(peer_ip.c_str());

    socklen_t len=sizeof(struct sockaddr_in);

    recv成功就返回实际接收的数据长度,失败返回-1;

    buf.assign(tmp,ret),把tmp中的ret个拷贝到buf中

    char *inet_ntoa(struct in_addr in);把网络字节序的IP地址转换为点分十进制的字符串IP地址

    ntohs,将网络字节序的16位数据,转换为主机字节序数据 

    客户端不推荐用户主动绑定固定地址,因为一个端口只能被一个进程占用,一旦端口固定,客户端程序只能启动一个
        
    socket没有 绑定地址,这时候操作系统在发送数据之前检测到,操作系统会为socket选择一个合适的地址和端口进行绑定

    访问只能访问公网地址,绑定的是本机地址

昨天看来是真的不适合写代码…怎么写怎么错…今天居然改着改着改对了…

代码示例:

USocket.hpp

#pragma once

#include<iostream>
#include<string>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>

using namespace std;

#define CHECK_RET(q) if((q)==false){return -1;}

class Socket
{
    public:

        bool Sock()
        {
            //int socket(int domain, int type, int protocol);
            _sockfd=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);

            if(_sockfd<0)
            {
                cout<<"socket error"<<endl;
                return false;
            }
            return true;
        }

        bool Bind(const string &ip,const uint16_t port)
        {
            //int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
            struct sockaddr_in addr;
            addr.sin_family=AF_INET;

            //uint16_t htons(uint16_t hostshort);
            addr.sin_port=htons(9000);

            //将点分十进制字符串IP地址转换为网络字节序格式的ip地址
            addr.sin_addr.s_addr=inet_addr(ip.c_str());

            int ret;
            socklen_t len=sizeof(struct sockaddr_in);

            ret=bind(_sockfd,(struct sockaddr*)&addr,len);

            if(ret<0)
            {
                cout<<"bind error"<<endl;
                return false;
            }
            return true;
        }

        bool Send(const string &data,const string &peer_ip,const uint16_t peer_port)
        {
            //ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                //const struct sockaddr *dest_addr, socklen_t addrlen);
            struct sockaddr_in addr;
            addr.sin_family=AF_INET;
            addr.sin_port=htons(peer_port);

            addr.sin_addr.s_addr=inet_addr(peer_ip.c_str());

            socklen_t len=sizeof(struct sockaddr_in);

            int ret=sendto(_sockfd,&data[0],data.size(),0,(struct sockaddr*)&addr,len);

            if(ret<0)
            {
                cout<<"send error"<<endl;
                return false;
            }
            return true;
        }

        bool Recv(string &buf,string &peer_ip,uint16_t &peer_port)
        {
            //ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        //struct sockaddr *src_addr, socklen_t *addrlen);
            struct sockaddr_in peer_addr;

            socklen_t len=sizeof(struct sockaddr_in);

            char tmp[4096]={0};

            int ret=recvfrom(_sockfd,tmp,4096,0,(struct sockaddr*)&peer_addr,&len);

            if(ret<0)
            {
                cout<<"recv error"<<endl;
                return false;
            }

            peer_ip=inet_ntoa(peer_addr.sin_addr);
            peer_port=ntohs(peer_addr.sin_port);

            buf.assign(tmp,ret);
            return true;
        }

        void Close()
        {
            close(_sockfd);
        }
        
    private:
        int _sockfd;
};

udp-client.cpp

#include"USocket.hpp"

#include<sstream>

int main(int argc,char *argv[])
{
    if(argc!=3)
    {
        cout<<"start wrong"<<endl;
        return -1;
    }

    uint16_t port;
    string ip=argv[1];

    stringstream tmp;
    tmp<<argv[2];
    tmp>>port;

    Socket sock;
    CHECK_RET(sock.Sock());

    while(1)
    {
        string buf;
		cout<<"client say:";
        cin>>buf;

        sock.Send(buf,ip,port);

        buf.clear();

        sock.Recv(buf,ip,port);
        cout<<"server say:"<<buf<<endl;
    }

    sock.Close();

    return 0;
}

udp-server.cpp

#include"USocket.hpp"

#include<sstream>

int main(int argc,char *argv[])
{
    if(argc!=3)
    {
        cout<<"start wrong"<<endl;
        return -1;
    }

    uint16_t port;
    string ip=argv[1];

    stringstream tmp;
    tmp<<argv[2];
    tmp>>port;

    Socket sock;
    CHECK_RET(sock.Sock());

    sock.Bind(ip,port);

    while(1)
    {
        string buf;

        sock.Recv(buf,ip,port);

		cout<<"client say:"<<buf<<endl;

        buf.clear();

		cout<<"server say:";
		cin>>buf;
        sock.Send(buf,ip,port);
    }

    sock.Close();
    
    return 0;
}

基于tcp协议的客户端与服务端通信流程:

客户端:

<1>.创建套接字

<2>.为套接字绑定地址信息

<3>.向服务端发起连接请求int connect(int sockfd, const struct sockaddr * addr,socklen_t addrlen);

    SYN--->ACK+SYN--->ACK

<4>.发送数据,ssize_t send(int sockfd, const void * buf, size_t len, int flags);

<5>.接收数据,ssize_t recv(int sockfd, void * buf, size_t len, int flags);

    MSG_PEEK:从接收缓冲区获取数据,但是不从缓冲区移除

<6>.关闭套接字,close(sockfd);

服务端:

<1>.创建套接字

<2>.为套接字绑定地址信息

<3>.开始监听listen(int sockfd,int backlog);

    开始监听就是告诉操作系统,若是有新的客户端连接请求过来,就为这个客户端完成三次握手建立连接的过程

    服务端收到客户端新的SYN连接请求,则会为这个客户端新建一个socket专门用于与这个客户端进行独立通信

    backlog:客户端的最大并发连接数,已完成连接队列(默认最小为1),未完成连接队列(满了之后的新请求被丢弃)

            已完成队列满,同时未完成连接队列中有完成连接的,依旧等待在未完成队列中

<4>.获取为客户端新建立连接的socket

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

    从已完成连接队列中阻塞获取一个已完成连接的socket,并且使用这个socket与指定的客户端进行数据通信

    (最早创建的socket只用于获取新连接请求)

UDP是只能客户端先发送请求,但是TCP谁先发都是可以的

<5>.发送数据,ssize_t send(int newfd, const void * buf, size_t len, int flags);

<6>.接收数据,ssize_t recv(int newfd, void * buf, size_t len, int flags);

<7).关闭套接字,close(sockfd);

注意事项:

    _socketfd=socket(AF_INET,SOCK_STREAM,IPROTO_TCP);

    bool Bind(string &ip,string &port);

    sstream tmp;tmp<<port;tmp>>addr.sin_port;

    addr.sin_addr.s_addr=inet_addr(ip.c_str());

    tcp的面向连接有一个三次握手建立连接的过程,使用int listen(int sockfd, int backlog);开始监听,并且完成三次握手建立连接过程

    int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);//addr:服务端地址信息   addrlen:地址信息长度

    网络字节序的转换

    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);//sockfd:监听套接字的描述符   addr:客户端地址信息

    返回值为新建连接的socket描述符--->与客户端进行数据通信

    发送数据使用的send接口中第一个参数sockfd,客户端是套接字描述符,服务端是新建的socket描述符,成功返回实际发送的长度,失败返回-1

    最后关闭的是监听的socket

    sudo netstat -anptu

    -a  查看所有信息

    -n  不以服务别名显示

在这里插入图片描述

代码示例:

tcpsocket.hpp

#pragma once

#include<iostream>
#include<sstream>
#include<stdio.h>
#include<string>
#include<unistd.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<arpa/inet.h>

using namespace std;

#define CHECK_RET(q) if((q)==false){return -1;}

class TcpSocket
{
    public:
        void SetFd(int fd)
        {
            _sockfd=fd;
        }

        int GetFd()
        {
            return _sockfd;
        }

        bool Socket()
        {
	    //int socket(int domain, int type, int protocol);
	    _sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
            if(_sockfd<0)
            {
                cout<<"sock error"<<endl;
                return false;
            }
            return true;
        }

        int strtoint(const string &s)
        {
            int num;
            stringstream tmp;
            tmp<<s;
            tmp>>num;
            return num;
        }
        bool Bind(const string &ip,const string &port)
        {
            struct sockaddr_in addr;

            addr.sin_family=AF_INET;
            addr.sin_port=htons(strtoint(port));
            addr.sin_addr.s_addr=inet_addr(ip.c_str());

            socklen_t len=sizeof(struct sockaddr_in);

            int ret=bind(_sockfd,(struct sockaddr*)&addr,len);

            if(ret<0)
            {
                cout<<"bind error"<<endl;
                return false;
            }
            return true;
        }
        
        bool Listen(const int backlog=5)
        {
            //int listen(int sockfd, int backlog);
            int ret=listen(_sockfd,backlog);

            if(ret<0)
            {
                cout<<"listen error"<<endl;
                return false;
            }
            return true;
        }

        bool Connect(const string &srv_ip,const string &srv_port)
        {
            //int connect(int sockfd, sockaddr *addr,socklen_t addrlen)
            struct sockaddr_in addr;
            addr.sin_family=AF_INET;
            addr.sin_port=htons(strtoint(srv_port));
            addr.sin_addr.s_addr=inet_addr(srv_ip.c_str());

            socklen_t len=sizeof(struct sockaddr_in);

            int ret=connect(_sockfd,(struct sockaddr*)&addr,len);

            if(ret<0)
            {
                cout<<"connect error"<<endl;
                return false;
            }
            return true;
        }

        bool Accept(TcpSocket& clientSock,string *ip=NULL,uint16_t *port=NULL)
        {
            struct sockaddr_in clientaddr;
            socklen_t len=sizeof(struct sockaddr_in);

            int newfd=accept(_sockfd,(struct sockaddr*)&clientaddr,&len);

            if(newfd<0)
            {
                cout<<"accept error"<<endl;
                return false;
            }

            clientSock.SetFd(newfd);
            if(ip!=NULL)
            {
                *ip=inet_ntoa(clientaddr.sin_addr);
            }
            if(port!=NULL)
            {
                *port=ntohs(clientaddr.sin_port);
            }
            return true;
        }

        bool Send(string &data)
        {
            int ret=send(_sockfd,&data[0],data.size(),0);

            if(ret<0)
            {
                cout<<"send error"<<endl;
                return false;
            }
            return true;
        }

        bool Recv(string &buf)
        {
            char tmp[4096]={0};
            int ret=recv(_sockfd,tmp,4096,0);

            if(ret<0)
            {
                cout<<"recv error"<<endl;
                return false;
            }
            else if(ret==0)
            {
                cout<<"connect shundown"<<endl;
                return false;
            }

            buf.assign(tmp,ret);
            return true;
        }

        void Close()
        {
            close(_sockfd);
        }
    private:
        int _sockfd;
};

tcp-server.cpp

#include<iostream>
#include<stdio.h>
#include"tcpsocket.hpp"

using namespace std;

int main(int argc,char *argv[])
{
    if(argc!=3)
    {
        cout<<"start wrong"<<endl;
        return -1;
    }

    TcpSocket lst_sock;

    CHECK_RET(lst_sock.Socket());

    CHECK_RET(lst_sock.Bind(argv[1],argv[2]));

    CHECK_RET(lst_sock.Listen());

    while(1)
    {
        TcpSocket clientsock;

        bool ret=lst_sock.Accept(clientsock);

        if(ret==false)
        {
            continue;
        }

        string buf;

        ret=clientsock.Recv(buf);

        if(ret==false)
        {
            clientsock.Close();
            continue;
        }
        cout<<"client say:"<<buf<<endl;

        buf.clear();

        cout<<"server say:"<<endl;
        fflush(stdout);

        cin>>buf;
        clientsock.Send(buf);
    }

    lst_sock.Close();

    return 0;
}

tcp-client.cpp

#include"tcpsocket.hpp"
#include<iostream>

using namespace std;

int main(int argc,char *argv[])
{
    if(argc!=3)
    {
        cout<<"start wrong"<<endl;
        return -1;
    }

    TcpSocket sock;

    CHECK_RET(sock.Socket());

    CHECK_RET(sock.Connect(argv[1],argv[2]));

    while(1)
    {
        string  buf;
        cout<<"client say:";
        fflush(stdout);
        cin>>buf;

        sock.Send(buf);

        buf.clear();

        sock.Recv(buf);
        cout<<"server say:"<<buf<<endl;
    }

    sock.Close();

    return 0;
}

Accept和Recv都会阻塞,只能与一个客户端通信一次,因为服务端不知道客户端的新连接请求/数据什么时候到来,因此在流程写死的情况下,就会阻塞,导致流程无法继续

解决方案:服务端为每一个新的客户端都创建一个进程/线程来与客户端进行通信,example:QQ收到不同的信息会打开不同的窗口,QQ主进程只有一个,但是可以创建很多线程

UDP无连接,只要知道地址,就可以发送,只使用一个socket

多进程版本:

父进程负责accept,子进程单独负责每个客户端

#include<iostream>
#include<stdio.h>
#include<signal.h>
#include<sys/wait.h>
#include"tcpsocket.hpp"

using namespace std;

void sigcb(int signo)
{
    while(waitpid(-1,NULL,WNOHANG)>0);
}

int main(int argc,char *argv[])
{
    if(argc!=3)
    {
        cout<<"start wrong"<<endl;
        return -1;
    }

    signal(SIGCHLD,sigcb);

    TcpSocket lst_sock;

    CHECK_RET(lst_sock.Socket());

    CHECK_RET(lst_sock.Bind(argv[1],argv[2]));

    CHECK_RET(lst_sock.Listen());

    while(1)
    {
        TcpSocket clientsock;

        bool ret=lst_sock.Accept(clientsock);

        if(ret==false)
        {
            continue;
        }

        if(fork()==0)
        {
            while(1)
            {
                string buf;

                ret=clientsock.Recv(buf);

                if(ret==false)
                {
                    clientsock.Close();
                    continue;
                }
                cout<<"client say:"<<buf<<endl;

                buf.clear();

                cout<<"server say:";
                fflush(stdout);

                cin>>buf;
                clientsock.Send(buf);
            }
            clientsock.Close();
        }
        clientsock.Close();
    }

    lst_sock.Close();

    return 0;
}

多线程版本:

线程之间共享文件描述符表,所以不可以使用close关闭,普通线程使用完之后close和delete

每个线程负责一个任务,防止多个任务交叉造成阻塞

#include<iostream>
#include<stdio.h>
#include"tcpsocket.hpp"

using namespace std;

void* thr_start(void* arg)
{   
    TcpSocket *clientsock=(TcpSocket*)arg;
    while(1)
    {
        string buf;

        clientsock->Recv(buf);

        cout<<"client say:"<<buf<<endl;

        buf.clear();

        cout<<"server say:";
        fflush(stdout);

        cin>>buf;
        clientsock->Send(buf);
    }

    clientsock->Close();
    delete clientsock;

    return NULL;
}

int main(int argc,char *argv[])
{
    if(argc!=3)
    {
        cout<<"start wrong"<<endl;
        return -1;
    }

    TcpSocket lst_sock;

    CHECK_RET(lst_sock.Socket());

    CHECK_RET(lst_sock.Bind(argv[1],argv[2]));

    CHECK_RET(lst_sock.Listen());

    while(1)
    {
        TcpSocket *clientsock=new TcpSocket();

        bool ret=lst_sock.Accept(*clientsock);

        if(ret==false)
        {
            continue;
        }

        pthread_t tid;
        pthread_create(&tid,NULL,thr_start,(void*)clientsock);

    }

    lst_sock.Close();

    return 0;
}

连接断开的体现:

当通信双方,连接断开时,recv返回0(表示连接断开),应该关闭套接字
send出发异常,发送SIGPIPE信号,会导致进程退出

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值