yo!这里是socket网络编程相关介绍

目录

前言

基本概念

源ip&&目的ip

源端口号&&目的端口号

udp&&tcp初识 

socket编程 

网络字节序

socket常见接口

socket

bind

listen

accept

connect

地址转换函数

字符串转in_addr

in_addr转字符串

套接字读写函数

recvfrom&&recv

sendto&&send

简单udp服务器客户端程序

udp服务器

udp客户端

简单tcp服务器客户端程序

接口理解

tcp服务器 

tcp客户端

后记


前言

        在学习网络部分知识时,只学习或只背诵概念是没有用的,面试过程中面试官肯定会究其细节或理解从各方面考察,因此上章节介绍完网络初步相关概念之后,我们先上代码去直观地了解网络,为后面更深层次或者复杂的概念或网络传输过程的细节打下基础。那么,在网络部分socket编程就是用于在不同计算机之间进行通信,因此本章节就是介绍socket编程相关接口以及运用其编写简单的程序去了解通信过程。

基本概念

  • 源ip&&目的ip

        在ip数据包的头部中存在两个IP地址——源ip&&目的ip,即此数据包从哪来&&去哪里。我们知道,IP地址(此处说的是公网ip),标定了主机的唯一性,但通过IP地址将数据发送到主机并不是完整的网络通信过程,也就是说一台主机的信息到达了目标主机上真的就算是数据发送成功了吗?并不是,其实网络通信过程的本质就是进程间通信(由一台主机的进程发送到另一台主机的进程),将数据在主机间转发只是手段,主机收到后需要将数据交付给相应进程。但是,数据到达目标主机后该走向哪个进程呢?这就需要下一个概念——端口号了。

  • 源端口号&&目的端口号

        端口号(2字节16位的整数)传输层的内容,标识特定主机上网络进程的唯一性,也就是标识一个进程,告诉os数据交给哪个进程来处理。可见,一个端口号只能被一个进程占用,但一个进程可以绑定多个端口号

        源端口号&&目的端口号存在于传输层的数据段中,标识哪个进程发的&&发给哪个进程。综上ip+端口号表示指定主机的指定进程,这是一个数据进入网络前要确定的东西。

  • udp&&tcp初识 

        先来简单初识一下TCP和UDP,在后面讲解传输层时会详解。TCP(Transmission Control Protocol 传输控制协议)和UDP(User Datagram Protocol 用户数据报协议)都是传输层协议,但TCP需要连接、可靠传输、面向字节流,而UDP无需连接、不可靠传输、面向数据报,如下图很具象地展现了TCP和UDP传输数据地过程。

        其中,(无需)连接说的是udp没有tcp在通信的三次握手与四次挥手,而是没有建立连接过程,即“发送即结束”;可靠传输说的是tcp传输数据是不会出错的,udp传输数据可以出错;面向字节流与面向数据报是指看待数据的方式不同,tcp需要根据应用层协议对字节流作序列化和反序列化识别出一个报文,而udp直接默认拿到的就是一个报文。但是,综上来看真的意思是tcp比udp好吗?其实不然,两者的使用需要看场景,tcp可以多应用在需要数据准确交付的场景,比如重要文件传输,而udp可以多应用在对传输错误也可容忍的场景,比如游戏,直播等实时性要求高的环境下(有时看直播会卡,但是无伤大雅)。

  • socket编程 

        Socket编程是一种网络编程技术,它提供了一种在不同计算机之间进行通信的方式。

基于TCP/IP协议,它允许计算机之间通过网络进行数据传输。Socket编程提供了一些函数和方法,用于建立连接、传输数据和关闭连接。

        在Socket编程中,有两个主要的角色:服务器和客户端。服务器程序监听指定的端口,等待客户端的连接请求。客户端程序通过指定服务器的IP地址和端口,发起连接请求。以上会在下面详细介绍。

网络字节序

        我们知道,内存中的多字节数据相对于内存地址有大端和小端之分,而网络数据流也同样有大端小端之分,因此,TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.。不管这台主机是大端机还是小端机,都要按照这个TCP/IP规定的网络字节序来发送/接收数据,如果当前发送主机是小端,就需要先将数据转成大端,否则就忽略,直接发送即可。那么,如下接口就是转大端的相关接口,调用其可以做网络字节序和主机字节序的转换。

        其中h表示host,n表示net,l表示32位长整数,s表示16位短整数,如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回,如果主机是大端字节序,这些函数将不做转换,将参数原封不动的返回。

socket常见接口

  • socket

功能:创建socket文件描述符(tcp和udp均会用到,服务器和客户端均需要

参数

        domain:对于ipv4,指定为AF_INET,对于ipv6,指定为AF_INET6

        type:对于UDP,指定为SOCK_DGRAM,表示面向数据报的传输协议,对于TCP,指定为SOCK_STREAM,表示面向字节流的传输协议

        protocol:指定为0即可

返回值:成功返回一个文件描述符,类似读写文件一样,向此文件描述符读写就是在网络上收发数据,否则返回-1

  • bind

功能:绑定端口号(tcp和udp均会用到,仅服务器需要,因为服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接,但是客户端程序的端口号通常是变化的,所以不需要bind),将参数sockfd和myaddr绑定在一起,使sockfd这个用于网络通讯的文件描述符监听addr所描述的地址和端口号

参数

        sockfd:需要绑定的sock套接字,其实就是socket函数的返回值

        addr:通用指针类型,可以接受多种协议的sockaddr结构体地址(多种sockaddr结构体类型在下面介绍),但长短不同,需要传入长度

        addrlen:addr指向的结构体长度

返回值:成功返回0,否则返回-1

sockaddr结构: 

         socket的接口适用于各种底层网络协议,但是各种网络协议的地址格式并不相同,又不想设计过多的接口,因此将所有接口进行统一,接口只有一套,协议格式统一使用struct sockaddr*,传其他协议地址(sockaddr_in、sockaddr_un,如下图)需要通过强转,但若需要知道是哪一种套接字地址,可以通过16位地址类型判断。

        这三者中,本章节常用到sockaddr_in结构,也就是说常用到将sockaddr结构体强转成sockaddr_in结构体,这里详细说明一下sockaddr_in结构体,以下为其内部结构,在这个结构体中,最主要的就是端口号和IP地址,即图中标星号的部分。

        在我们写的程序中一般是这样初始化sockaddr结构体的(如下代码块),首先清零,再指定地址类型,然后填入ip和端口号信息。注意转大端字节序,并且其中INADDR_ANY为宏(实则为0.0.0.0),表示本地的任意IP地址,则最后一个语句代表若需要指定ip地址则赋值给_ip,否则使用任意IP地址。

代码:

struct sockaddr_in peer;
memset(&peer, 0, sizeof(peer));
peer.sin_family = AF_INET;
peer.sin_port = htons(_port);
peer.sin_addr.s_addr = _ip.size() == 0 ? INADDR_ANY : inet_addr(_ip.c_str());
  • listen

功能:开始监听socket(仅tcp会用到,仅服务器需要

参数

        sockfd:如上

        backlog:表示最多允许有backlog个客户端处于连接等待状态,如果接收到更多的连接请求就忽略,常设置为20,后面会详细讲到

返回值:成功返回0,否则返回-1

  • accept

功能:接受请求(仅tcp会用到,仅服务器需要),三次握手完成后,服务器调用accept()接受连接,如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来

参数

        sockfd:如上

        addr和addrlen:输出型参数,需提前定义传入函数,函数返回后带出来对方的ip和端口号信息,若传NULL,则代表不关心客户端的地址

返回值:成功返回一个文件描述符,否则返回-1

  • connect

功能:建立连接(仅tcp会用到,仅客户端需要,因为客户端需要调用此函数连接服务器)

参数

        sockfd:自己(客户端)的fd

        addr和addrlen:传入对方的IP地址和端口号信息

返回值:成功返回0,失败返回-1

地址转换函数

        在上面介绍到的sockaddr中,sockaddr_in的成员sin_addr(struct in_addr)表示32位的IP地址,但是我们通常都是用点分十进制(比如192.168.1.10)表示IP地址,因此需要函数可以将字符串表示和in_addr之间来回转换。

  • 字符串转in_addr

        首先,三者的功能是一样的——将字符串转in_addr,先看前两个,cp就是需要转换的字符串,inp是一个输出型参数,结果由此参数传出,而第二个函数是直接返回出来,第三个函数较于前两个函数是新的,其中af可以填AF_INET或AF_INET6表示ipv4或ipv6,src还是传入需要转换的字符串,dst与inp的功能是一样的,只是需要接收ipv4的in_addr或ipv6的in6_addr,因此设置为void*可以同样接收(强转)。

  • in_addr转字符串

        同样地,in传入in_addr,函数返回转换地字符串形式,而inet_ntop是新接口,可以接收ipv6,src就是传入不同类型的in_addr,dst接收转换的字符串指针,size传入字符串指针可以接收的字符串大小。

套接字读写函数

  • recvfrom&&recv

        注意,recv用于tcp,recvfrom函数用于udp。可以看到,recvfrom函数除了比recv函数多了两个参数,其他的参数是一样的,因此只要介绍recvfrom即可。

sockfd:读写的套接字(文件描述符)

buffer&&len:读取的数据所放的缓冲区及大小

flags:控制接收行为,通常可设为0

src_addr&&addrlen:输出型参数,用来接收数据来源的地址信息及大小,注意addrlen在传入时就要是sre_addr的大小

返回值:正常返回接收成功的字符个数若数据读完或套接字关闭,则返回值为0,若出错则返回-1并设置错误码

  • sendto&&send

        注意,send用于tcp,sendto函数用于udp。同样地,sendto函数也是比send函数多了两个参数。

sockfd:读写的套接字(文件描述符)

buf&&len:指向需要发送的数据的字符串指针及大小

flags:同recvfrom函数

dest_addr&&addrlen:不是输出型函数,需要传入需要发送的目的地址信息及大小

返回值:同recvfrom函数

注意:

        思考一下recvfrom函数和sendto函数的使用顺序,比如说,你使用sendto函数给张三发信息,此时你需要知道张三的地址信息,张三使用recvfrom函数接收到了你的信息,并且他通过输出型参数得到了你的地址信息,之后张三拿着你的地址信息使用sendto函数给你回信,你通过recvfrom函数拿到了张三的回信并且拿到了他的地址信息。

        在这个过程中,你与张三通信的前提是你知道张三的地址信息,你其实是知道的,无论是之前见面留下的联系方式还是曾经通信过,你们之间必须至少有一方是知道的,否则无法通信。类比到服务器与客户端,“你”就是客户端,“张三”就是服务器,一般来说都是客户端主动给服务器发信息,客户端都是提前知道服务器的地址信息的,客户端给服务器发送请求,服务器处理需求,将结果回信给客户端,客户端拿到需求即为享受到服务。

简单udp服务器客户端程序

  • udp服务器

        下面的代码块是udp服务器的封装及主程序调用。先看udp服务器封装的类,属性包括监听的文件描述符sockfd、IP地址、端口,这些是一个udp服务器最基本需要的属性,构造函数中传入所决定的端口号、IP地址、sock(还没有生成,初始化为-1)。

        在启动udp服务器前,先做一下准备工作——initserver(),创建监听的文件描述符sock和绑定地址信息(包括端口号和IP地址),之后启动udp服务器——start(),显然是死循环运行,作为服务器,我们需要先使用recvfrom函数接收客户端的请求(数据),返回值大于0,说明正确读到了数据,这里当作字符串打印出来,并调用sendto函数将读到的字符串写回给客户端(处理方式有很多,也可以客户端传来两个数字,服务器计算出结果将其返回,但是这涉及到http协议的设定及序列化、反序列化)。最后若关闭服务器,莫要忘记close监听的sockfd。

        可以看到,udp服务器只能用到前面所谈到的socket常用接口其中的两个——socket()、bind(),这可以联想到udp的特性——无连接,而listen()、accept()、connect()意味着是有连接,在tcp服务器的实现中才会用到。

代码:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log.hpp"
using std::cout;
using std::endl;
#define SIZE 1024

class UdpServer
{
public:
    UdpServer(uint16_t port,std::string ipAddr="")
        :_port(port)
        ,_ipAddr(ipAddr)
        ,_sock(-1)
    {

    }

    bool initserver()
    {
        //1.创建套接字(ip+port)
        _sock=socket(AF_INET,SOCK_DGRAM,0);
        if(_sock<0)
        {
            logMessage(FATAL,"%d:%s",errno,strerror(errno));
            exit(2);        
        }
        logMessage(NORMAL,"create socket success");

        //2.通过bind函数将此ip和端口号与此进程绑定
        struct sockaddr_in local;
        bzero(&local,sizeof(local)); //设置内容之前将所有字段设置为0
        local.sin_family=AF_INET;
        //端口需要发送给对方主机,需要通过网络,因此将数据的字节序改为大端存储
        local.sin_port=htons(_port);
        //ip也是一样,但同时还需要将ip的字符串形式改为4个字节存储,因此4个字节足够存储一个ip,
        //比如说192.168.10.1,每段数字都是[0,255],用1字节即可存储
        //因此使用inet_addr函数,可同时完成上面两件事
        //同时,INADDR_ANY表示服务器在工作过程中,可以从任意IP中获取数据,也就是说不建议bind一个确定的地址
        local.sin_addr.s_addr = _ipAddr.size()==0 ? INADDR_ANY : inet_addr(_ipAddr.c_str());

        if(bind(_sock,(struct sockaddr*)&local,sizeof(local))<0)
        {
            logMessage(FATAL,"%d:%s",errno,strerror(errno));
            exit(3);
        }
        logMessage(NORMAL,"bind success");
        return true;

    }

    void start()
    {
        char buffer[SIZE];
        for( ; ; )
        {
            //读取数据
            struct sockaddr_in peer;
            socklen_t len=sizeof(peer);
            ssize_t rf=recvfrom(_sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);
            if(rf>0)
            {
                buffer[rf]=0; //缓冲区末尾放上‘\0’,当作字符串
                uint16_t cli_port=ntohs(peer.sin_port);
                std::string cli_ip=inet_ntoa(peer.sin_addr);
                printf("[%s:%d]: %s\n",cli_ip.c_str(),cli_port,buffer);
            }

            //分析处理数据
            //写回数据
            sendto(_sock,buffer,sizeof(buffer),0,(struct sockaddr*)&peer,len);
        }
    }

    ~UdpServer()
    {
        if(_sock>=0) close(_sock);
    }
private:
    uint16_t _port;
    std::string _ipAddr;
    int _sock;
};


static void usage(std::string proc)
{
    cout<<"\nUsage:"<<proc<<" port\n"<<endl;
}

int main(int argc,char* argv[])
{
    if(argc!=2)
    {
        usage(argv[0]);
        exit(1);
    }

    //std::string ip=argv[1];
    uint16_t port=stoi(argv[1]);
    std::unique_ptr<UdpServer> server(new UdpServer(port));
    server->initserver();
    server->start();
    return 0;
}

  • udp客户端

        下面代码块是udp客户端的实现(主程序实现,未封装udp客户端),与服务器实现并没有本质的不同(但可以发现客户端不需要bind)。通过命令行参数传入服务器的地址信息(端口号和ip地址),首先依旧是创建监听的文件描述符sock,之后就可以将服务器的地址信息填进struct sockaddr_in中,等待后面的sendto函数的使用,但是会发现客户端无需bind,为什么?因为客户端通过哪个端口与服务器通信并不重要,socket函数的函数体中会自动为客户端程序选择一个未被占用的端口号,并不需要用户去操心,那又为什么服务器程序需要指定端口号呢?因为服务器的端口号和IP地址是需要固定的,不然客户端程序如何得知新的变化的端口号和IP地址,因此需要调用bind函数固定下来。

        显然,客户端程序主体也是一个死循环,先产生用户的需求,之后调用sendto函数将需求发给服务器程序,服务器处理之后,客户端调用rcvfrom函数接收结果,这里将结果显示出来(举例而已,也可以将结果作为下一次传入的需求),若客户端程序关闭,也莫要忘记关闭监听的文件描述符。

代码:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <string.h>
#include <unistd.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using std::cout;
using std::endl;
#define SIZE 1024

static void usage(std::string proc)
{
    cout<<"\nUsage:"<<proc<<" ip port\n"<<endl;
}

int main(int argc,char* argv[]) //传入服务器ip、port
{
    if(argc!=3)
    {
        usage(argv[0]);
        exit(1);
    }

    int sock=socket(AF_INET,SOCK_DGRAM,0);
    if(sock<0)
    {
        std::cerr<<"socket error"<<endl;
        exit(2);
    }
    uint16_t serverport=atoi(argv[2]);
    std::string serverip=argv[1];
    std::string message; //从键盘拿信息
    char buffer[SIZE]; //缓冲区
    struct sockaddr_in server;
    memset(&server,0,sizeof(server)); //提前置零
    server.sin_addr.s_addr=inet_addr(serverip.c_str());
    server.sin_port=htons(serverport);
    while(true)
    {
        cout<<"请输入:";
        std::getline(std::cin,message);
        if(message=="quit")
            break;
        sendto(sock,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));

        struct sockaddr_in tmp;
        socklen_t len=sizeof(tmp);
        ssize_t rf=recvfrom(sock,buffer,sizeof(buffer),0,(struct sockaddr*)&tmp,&len); //tmp与server相同,都是服务端ip、port相关信息

        if(rf>0)
        {
            buffer[rf]=0;
            cout<<"server # "<<buffer<<endl;
        }
    }
    close(sock);

    return 0;
}

 运行结果:

简单tcp服务器客户端程序        

  • tcp服务器 

        以下代码块是tcp服务器的封装及主程序调用,先看tcp服务器封装,属性和构造函数与udp服务器封装一样。对于initserver(),可以看到,调用socket创建一个文件描述符及bind()绑定地址信息也都是一样的,但是tcp服务器紧接着调用listen()监听sockfd是否有客户端连接,这是tcp服务器必须要做的,其实本质就是使sockfd成为一个监听描述符。

        之后,依旧是死循环开启tcp服务器——start()。首先,服务器会调用accept函数去阻塞等待客户端的连接,当有客户端调用connect函数去建立连接时,accept函数就会返回一个新的文件描述符serverfd,这个文件描述符专门用来和客户端通信,而原本的监听描述符继续监听客户端的connect函数申请连接,若存在就会继续返回另一个新的文件描述符与此客户端通信。这个过程就好像是,监听文件描述符是一个在饭店门口拉客的服务员,当拉到客人之后进店就会找另一个服务员(与此客户端通信的文件描述符)来接待这个客人(比如点菜,服务),而拉客服务员继续去门外拉客。

        当accept函数返回一个新的文件描述符来与客户端通信,服务器就可以通过此文件描述符收到客户端的需求,并返回应答给客户端,这里封装成了一个服务函数——service(),首先recv函数(也可以read函数)读取客户端的数据,处理以后通过write函数(也可以send函数)返回结果给客户端,这里依旧是把数据当作字符串并写回给客户端。

代码:

#include <iostream>
#include <string>
#include <string.h>
#include <cstdlib>
#include <cstdio>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log.hpp"

using std::cout;
using std::endl;
using std::string;

static void service(int servicesock,string cli_ip,uint16_t cli_port)
{
    char buffer[1024];
    while(true)
    {
        int n=read(servicesock,buffer,sizeof(buffer)-1);
        if(n>0)
        {
            buffer[n]='\0';
            cout<<cli_ip<<"-"<<cli_port<<"# "<<buffer<<endl;
        }
        else if(n==0) //连接被关闭了
        {
            logMessage(NORMAL,"link shutdown");
            break;
        }
        else
        {
            logMessage(ERROR,"read error");
            break;
        }

        string msg;
        msg+=buffer;
        write(servicesock,msg.c_str(),msg.size());
    }
}

class TcpServer
{
private:
    const static int g_backlog=20;
public:
    TcpServer(uint16_t port,string ip="")
        :_port(port)
        ,_ip(ip)
        ,_sock(-1)
    {}

    void initserver()
    {
        //创建socket
        _sock=socket(AF_INET,SOCK_STREAM,0);
        if(_sock<0)
        {
            logMessage(FATAL,"create socket error");
            exit(2);
        }
        //bind
        struct sockaddr_in peer;
        memset(&peer,0,sizeof(peer));
        peer.sin_port=htons(_port);
        peer.sin_family=AF_INET;
        peer.sin_addr.s_addr=_ip.size()==0 ? INADDR_ANY : inet_addr(_ip.c_str());
        if(bind(_sock,(struct sockaddr*)&peer,sizeof(peer))<0)
        {
            logMessage(FATAL,"bind error");
            exit(3);
        }

        //listen函数用以建立连接,相当于饭店的一个服务员在外面拉客,其中_sock就是在外面拉客的服务员
        if(listen(_sock,g_backlog)<0)
        {
            logMessage(FATAL,"listen error");
            exit(4);
        }

        logMessage(NORMAL,"initserver success");
    }

    void start()
    {
        while(true)
        {
            struct sockaddr_in peer;
            socklen_t len=sizeof(peer);
            int servicesock=accept(_sock,(struct sockaddr*)&peer,&len); //_sock将拉的客人带到饭店,店里接待的服务员出来一个招呼他,其中servicesock就是一个招待的服务员
            if(servicesock<0)
            {
                logMessage(ERROR,"accept error");
                continue;
            }
            logMessage(NORMAL,"accept success");

            uint16_t clientport=ntohs(peer.sin_port);
            string clientip=inet_ntoa(peer.sin_addr);

            service(servicesock,clientip,clientport);
        }
    }

    ~TcpServer()
    {
        if(_sock>0)
            close(_sock);
    }
private:
    uint16_t _port;
    string _ip;
    int _sock;
};


static void usage(string proc)
{
    cout<<"usage: "<<proc<<" port"<<endl;
}

//    ./tcpserver port
int main(int argc,char* argv[])
{
    if(argc!=2)
    {
        usage(argv[0]);
        exit(1);
    }
    uint16_t serverport=atoi(argv[1]);
    unique_ptr<TcpServer> server(new TcpServer(serverport));
    server->initserver();
    server->start();

    return 0;
}

  • tcp客户端

        tcp客户端也并未做出封装,依旧是在主程序中直接实现,与udp客户端程序实现的差别在于socket函数创建文件描述符(依旧不需要bind函数)之后,调用connect函数与服务器建立连接,成功以后就可以正常的通信了,这一点与udp客户端实现也是一样。

        这里重点讲的就是,其实connect函数就是在发起三次握手,三次握手成功以后服务器的accept函数就会返回一个与客户端通信的文件描述符。也可以提一嘴,四次挥手是在客户端关闭(close函数)了socket函数创建出的文件描述符,详细会在传输层详解中讲到。

代码:

#include <iostream>
#include <string>
#include <string.h>
#include <cstdlib>
#include <cstdio>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "thread.hpp"
#include "log.hpp"

using std::cout;
using std::cerr;
using std::endl;
using std::string;

static void usage(string proc)
{
    cout<<"usage: "<<proc<<" port"<<endl;
}

//   ./tcpclient serverip serverport
int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        usage(argv[0]);
        exit(1);
    }
    string serverip=argv[1];
    uint16_t serverport=atoi(argv[2]);

    int sock=socket(AF_INET,SOCK_STREAM,0);
    if(sock<0)
    {
        cerr<<"create socket error"<<endl;
        exit(2);
    }

    struct sockaddr_in server;
    memset(&server,0,sizeof(server));
    server.sin_family=AF_INET;
    server.sin_port=htons(serverport);
    server.sin_addr.s_addr=inet_addr(serverip.c_str());
    
    if(connect(sock,(struct sockaddr*)&server,sizeof(server))<0)
    {
        cerr<<"connect error"<<endl;
        exit(3);
    }
    cout<<"connect success"<<endl;

    while(true)
    {
        string msg;
        cout<<"请输入:";
        getline(cin,msg);
        if(msg=="quit")
            break;

        send(sock,msg.c_str(),msg.size(),0);

        char buffer[1024];
        ssize_t s=recv(sock,buffer,sizeof(buffer)-1,0);
        if(s>0)
        {
            buffer[s]=0;
            cout<<"server# "<<buffer<<endl;
        }
        else if(s==0)
        {
            cout<<"link break"<<endl;
            break;
        }
        else
            break;
    }
    close(sock);
    return 0;
}

 运行结果:

后记

        在本章节中,我们先从网络编程所需要的概念基础下手,再从运用的视角去详细介绍了各种供数据在网络中传输的socket接口,接着编写简单的udp、tcp服务器综合使用这些接口加以巩固。虽接口看起来很多并且细节也多而且复杂,但是用于网络数据传输的接口大部分也就涉及到这些,也就是说只要掌握这些,网络传输的功能就掌握了一大半了,因此在学习这些接口时要仔细,最后的简单udp、tcp服务器可以反复的独立编写,加强记忆,在学习后面较为复杂的服务器时可以相对轻松,加油。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/615638.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

线性集合:ArrayList,LinkedList,Vector/Stack

共同点&#xff1a;都是线性集合 ArrayList ArrayList 底层是基于数组实现的&#xff0c;并且实现了动态扩容&#xff08;当需要添加新元素时&#xff0c;如果 elementData 数组已满&#xff0c;则会自动扩容&#xff0c;新的容量将是原来的 1.5 倍&#xff09;&#xff0c;来…

STK与matlab交互 Astrogator模块(14)

一、背景介绍 高轨卫星的轨道保持。与任何其它轨道状态一样&#xff0c;地球同步轨道也会受到各种扰动力的影响&#xff0c;这些摄动力会影响GEO卫星在位置方面的稳定性。摄动的主要来源是地球的非地球位势、太阳辐射压力和第三体效应&#xff08;主要是月球和太阳&#xff09…

特产销售|基于Springboot+vue的藏区特产销售平台(源码+数据库+文档)​

目录 基于Springbootvue的藏区特产销售平台 一、前言 二、系统设计 三、系统功能设计 1系统功能模块 2管理员功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂码农|毕设布道…

JavaScript 防抖与节流——以游戏智慧解锁实战奥秘

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 &#x1f3ae; 引言❓ 什么是防抖和节流&#x1f3f9; 防抖(Debounce) - 锁定追击&#xff0c;精确无误&#x1f4cc; 基础概念&#x1f4cc; 适用场景&#x1f4cc; 实战代码&#xff1a;防抖 应用于输入框的实时搜索 &…

【Python-爬虫】

Python-爬虫 ■ 爬虫分类■ 1. 通用网络爬虫&#xff1a;&#xff08;搜索引擎使用&#xff0c;遵守robots协议&#xff09;■ robots协议&#xff08;君子协议&#xff09; ■ 2. 聚集网络爬虫&#xff1a;自己写的爬虫程序 ■ urllib.request&#xff08;要导入的模块&#x…

带有-i选项的sed命令在Linux上执行成功,但在MacOS上失败了

问题&#xff1a; 我已经成功地使用以下 sed 命令在Linux中搜索/替换文本&#xff1a; sed -i s/old_string/new_string/g /path/to/file然而&#xff0c;当我在Mac OS X上尝试时&#xff0c;我得到&#xff1a; command i expects \ followed by text我以为我的Mac运行的是…

高效文件管理:一键提取文件名关键字,快速创建对应文件夹

在数字化时代&#xff0c;文件管理成为我们日常工作中不可或缺的一部分。随着文件数量的不断增加&#xff0c;如何高效、有序地管理这些文件成为了许多人的挑战。传统的文件管理方法&#xff0c;如手动创建文件夹和分类文件&#xff0c;不仅耗时耗力&#xff0c;而且容易出错。…

使用html和css实现个人简历表单的制作

根据下列要求&#xff0c;做出下图所示的个人简历&#xff08;表单&#xff09; 表单要求 Ⅰ、表格整体的边框为1像素&#xff0c;单元格间距为0&#xff0c;表格中前六列列宽均为100像素&#xff0c;第七列 为200像素&#xff0c;表格整体在页面上居中显示&#xff1b; Ⅱ、前…

多功能投票小程序基于ThinkPHP+FastAdmin+Uniapp(源码搭建/上线/运营/售后/维护更新)

基于ThinkPHPFastAdminUniapp开发的多功能系统&#xff0c;支持图文投票、自定义选手报名内容、自定义主题色、礼物功能(高级授权)、弹幕功能(高级授权)、会员发布、支持数据库私有化部署&#xff0c;Uniapp提供全部无加密源码。 功能特性

Vue-watch监听器

监听器 watch侦听器&#xff08;监视器&#xff09;简单写法完整写法 watch侦听器&#xff08;监视器&#xff09; 作用&#xff1a;监视数据变化&#xff0c;执行一些业务逻辑或异步操作 语法&#xff1a; watch同样声明在跟data同级的配置项中简单写法&#xff1a; 简单类型…

ios 开发如何给项目安装第三方库,以websocket库 SocketRocket 为例

1.brew 安装 cococapods $ brew install cocoapods 2、找到xcode项目 的根目录&#xff0c;如图&#xff0c;在根目录下创建Podfile 文件 3、在Podfile文件中写入 platform :ios, 13.0 use_frameworks! target chat_app do pod SocketRocket end project ../chat_app.x…

攻防世界-web-fileinclude

题目 解题 原题代码 <html> <head><meta http-equiv"Content-Type" content"text/html; charsetutf-8" /></head><b>Notice</b>: Undefined index: language in <b>/var/www/html/index.php</b> on lin…

【硬件模块】ESP-01SWiFi模块基于AT指令详解(WiFi,TCP/IP,MQTT)

ESP-01S ESP-01S是由安信可科技开发的一款Wi-Fi模块。其核心处理器是ESP8266&#xff0c;该处理器在较小尺寸的封装中集成了业界领先的Tensilica L106超低功耗32位微型MCU&#xff0c;带有16位精简模式&#xff0c;主频支持80MHz和160MHz&#xff0c;并集成了Wi-Fi MAC/BB/RF/P…

windows@注册表介绍@注册表的查看和编辑操作

文章目录 abstractrefs注册表的主要组件包括根键极其缩写名称&#x1f47a;子键特性 查看注册表&#x1f47a;使用powershell查看路径下的子路径声明概念Get-ChildItem查看注册表路径下的项Set-Location进入注册表路径举例说明查看文件系统某个路径下的项查看某个注册表路径的项…

笨方法自学python(二)-注释

注释和#号 程序里的注释是很重要的。它们可以用自然语言告诉你某段代码的功能是什么。在你想要临时移除一段代码时&#xff0c;你还可以用注解的方式将这段代码临时禁用。 # A comment, this is so you can read your program later. # Anything after the # is ignored by py…

Ubuntu磁盘剩余空间不足,空间异常

近日发现用了3年的Ubuntu系统笔记本磁盘空间极度告急&#xff0c;上网搜了一下都是讲解如何扩容、如何重新挂载空间&#xff0c;但是博主发现/home目录明明分配了200G的空间&#xff0c;但是只剩下6G可用&#xff0c;查询所有的文件夹发现&#xff0c;所有文件加起来已使用50G左…

串口数据的发送(单词的发送)and UART原理协议---第九天

1.在中断函数中&#xff0c;定义一个数组给SBUF&#xff0c; i数组的偏移以便输入单词&#xff0c;&#xff1b; 用strstr&#xff08;&#xff09;函数来比较cmd输入的单词里面的 "en" , " se ";亮灯后i回来原来的位置0&#xff0c;清空cmd, UART 原理…

二进制转为HEX数组小工具

在使用RA8889时&#xff0c;JPG的解码只能从FLASH的DMA通道获取&#xff0c;那么如果要从远端、或者SD卡等处读取JPG图片出来显示怎么办&#xff1f; RA8889支持JPG图片硬解码&#xff0c;但数据流是从FLASH进行DMA读取的&#xff0c;然后再进行解码。因此这种情况下&#xff…

01.Net基础知识

.Net的用途 Web、移动、云、桌面、游戏开发、物联网 &#xff08;IDE&#xff1a;集成开发环境&#xff09; .Net学习资源 Microsoft Learn、GitHub、G码云&#xff08;Gitee&#xff09; Visual Studio初步使用 1&#xff09;可创建的项目种类&#xff08;主要学习以下四…

从loss角度理解LLM涌现能力

如今的很多研究都表明小模型也能出现涌现能力&#xff0c;本文的作者团队通过大量实验发现模型的涌现能力与模型大小、训练计算量无关&#xff0c;只与预训练loss相关。 作者团队惊奇地发现&#xff0c;不管任何下游任务&#xff0c;不管模型大小&#xff0c;模型出现涌现能力…