(二)socket收发准备函数——网络编程

(1)主机字节序与网络字节序

        字节序是指内存多于一个字节类型的数据在内存中的存放顺序。

        一种是将低序字节存储在起始地址,这种称为小端(little-endian)字节序。

        一种是将高序字节存储在起始地址,这种称为大端(big-endian)字节序。

         两种字节序没有标准,两种格式都有系统使用。

        只有大于一个字节类型的数据在内存中的存放有顺序,一个字节的数据没有顺序的问题。

        主机字节序(host-byte):不同的机器主机字节序不相同,与CPU设计有关,与操作系统无关。

        由于不同机器之间对同一个数据的存放顺序不一样,使得不同体系结构之间无法通信,所以在通信时需要将数据转换成一种约定的字节序,即网络字节序。

        网络字节序(network-byte):网络字节序是TCP/IP中规定好的一种数据表示格式,其采用大端字(big-endian)节序。与CPU类型、操作系统等无关。

        网络字节序和主机字节序之间的转换函数:

        1)htons() 和 ntohs() 完成16位(short)无符号数的相互转换。

        2)htonl() 和 ntohl() 完成32位(long)无符号数的相互转换。

        所以在socket通讯时,指定ip地址(32位)及通讯端口(16位)时都将其做了对应转换。

serveraddr->sin_addr.s_addr = htonl(INADDR_ANY);                // 任意ip地址
serveraddr->sin_port = htons(atoi(argv[1]));    // 指定通信端口

(2)sockaddr_in结构体

         其原始结构体定义:

struct sockaddr {
    unsigned short sa_family;    // 地址类型 AF_XXX
    char sa_data[14];            // 14字节存放端口和地址
};

        此结构存放地址及端口号不方便,于是

        结构体定义如下:

struct sockaddr_in {
    short int sin_family;           // 地址类型 AF_INET(ipv4)
    unsigned short int sin_port;    // 端口号
    struct in_addr sin_addr;        // 地址
    unsigned char sin_zero[8];      // 为了保持与 struct sockaddr 一样的长度
};

struct in_addr {    // 简化定义  详细定义可百度
    unsigned long s_addr;    // 地址
};

         sin_zero[8]为了保持与 struct sockaddr 一样的长度,使得两个结构体可以强制转换。

        端口号 sin_port:0不使用

        1)[1, 1023]:熟知端口,IANA把这些端口号指派给了TCP/IP体系中最重要的一些应用协议。

        2)[1024, 49151]:为没有熟知端口号的应用程序使用。使用这类端口号必须在IANA按规定的手续登记,以防止重复。

        3)[49152, 65535]:留给客户进程选择暂时使用。

        端口号只具有本地意义,只是为了标识本计算机应用层中的各进程。

(3)gethostbyname()与inet_addr()

        1)in_addr_t  inet_addr(const char* cp):将字符串形式的ip地址转换为网络字节顺序的整型值。函数成功,返回非0,函数不正确则会返回0。

        2)char* inet_ntoa(struct in_addr in):把网络字节序的IP地址转换成字符串的IP地址。

        3)gethostbyname()定义如下:

struct hostent *gethostbyname(const char *name);

        参数:name,可以是域名或者主机名,例如“192.168.1.3”、“www.baidu.com”等,仅支持ipv4。

        返回值:如果成功,返回一个hostent结构指针,失败返回NULL。

        hostent结构体定义如下:

struct hosttent {
    char* h_name;        // 主机规范名
    char** h_aliases;    // 主机的别名
    int h_addrtype;       // 表示的是主机ip地址的类型。AF_INET/AF_INET6
    int h_length;        // 主机ip地址的长度
    char** h_addr_list;    // 主机的ip地址,网络字节序存储
};
# define h_addr h_addr_list[0]

        将获得的ip地址拷贝到sockaddr_in中:

memcpy(&servaddr->sin_addr, h->h_addr, h->h_length);

(3)socket()

        socket函数用于创建一个新的socket,也就是向系统申请一个socket资源。

        函数声明:

int socket(int domain, int type, int protocol);

        domain:协议域,又称协议族(family)。常用的协议族有AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX、Unix域Socket)、AF_ROUTE等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合,AF_UNIX决定了要用一个绝对路径名作为地址。

        type:指定socket类型。常用的socket类型有SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等。流式socket(SOCK_STREAM)是一种面向连接的socket,针对于面向连接的TCP服务应用。数据报式socket(SOCK_DGRAM)是一种无连接的socket,对应于无连接的UDP服务应用。

        protocol:指定协议。常用协议有IPPROTO_TCP、IPPROTO_UDP、IPPROTO_STCP、IPPROTO_TIPC等,分别对应TCP传输协议、UDP传输协议、STCP传输协议、TICP传输协议。

        常用参数为ipv4,TCP连接,故参数分别为AF_INET、SOCK_STREAM、IPPROTO_TCP(0)。

        返回值:成功返回一个socket(int),失败返回-1。错误原因存于errno 中。

(4)bind()

        bind函数用于服务端将用于通讯的地址和端口绑定到socket上。

        函数声明:

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

        socket:需要绑定的socket

        addr:存放了服务器端用于通信的地址和端口,类型为struct sockaddr *,故使用sockaddr_in结构体定义通信地址和端口时,需强制类型转换

bind(listenfd, (struct sockaddr *) serveraddr, sizeof(*serveraddr))

        addrlen:表示addr结构体的大小

        返回值:成功则返回0, 失败返回-1,错误原因存于errno中。

        如果绑定的地址错误,或端口已被占用,bind函数一定会报错,否则一般不会返回错误。

        服务端程序的端口释放后可能会处于TIME_WAIT状态,需等待两分钟之后才能再被使用,可以通过设置SO_REUSEADDR属性可以让端口释放后立即再次使用。

// 设置SO_REUSEADDR选项
int opt = 1;
unsigned int len = sizeof(opt);
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, len);

(5)listen()

        listen函数把主动连接socket变为被动连接的socket,使得这个socket可以接受其他socket的连接请求,从而成为一个服务端的socket。

        函数声明:

int listen(int socket, int backlog);

        socket:一个已经被bind过的socket。socket()函数返回的是一个主动连接的socket,在服务器端的编程中,程序员希望这个socket可以接受外来的连接请求,也就是被顶等待客户端来连接。由于系统默认认为一个socket是主动连接的,所以需要通过某种方式来告诉系统,即使用listen()函数。

        backlog:此参数涉及网络细节,一般填5, 10 都行,一般不超过30。

        返回值:0成功,-1失败,错误原因存于errno中。

        当调用listen之后,服务端的socket就可以调用accept来接受客户端的连接请求。

(6)accept()

        accept函数从已准备好的连接队列中获取一个请求,如果队列为空,accept函数将阻塞等待。

        函数声明:

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

         socket:一个已经被listen过的socket。

        addr:用于存放客户端的地址信息,用socketaddr结构体表达,如果不需要客户端的地址,可以填0。

        addrlen:用于存放addr参数的长度,如果addr为0,addrlen也填0。

        返回值:返回一个新的socket,服务端使用这个新的socket和客户端进行报文的收发。

        accept在等待的过程中,如果被中断或其他原因,函数返回-1,表示失败,错误原因存于errno中,可以重新accept。

(7)connect()

        connect函数用于客户端向服务端发起连接请求。

        函数声明:

int connect(int sockfd, struct sockaddr* serv_addr, int addrlen);

        sockfd:用于连接服务器端的socket。

        serv_addr:存储有连接服务器地址及端口。

        addrlen:结构体sockaddr的长度。

        返回值:0成功,-1失败,错误原因存于errno中。

        当服务端地址错误、端口错误或服务端没有启动时,connect一定会失败。

小结:

        服务端:socket->bind->listen->accept

        客户端:socket->connect 

        1)服务端在调用listen()之前,客户端不能向服务端发起连接请求。

        2)服务端调用listen()之后,服务端的socket开始监听客户端的连接。

        3)客户端调用connect()函数向服务端发起连接请求。

        4)在TCP底层,客户端和服务端握手后建立起通信通道,如果有多个客户端请求,在服务端就会形成一个已准备好的连接的队列。

        5)服务端调用accept()函数从队列中获取一个已准备好的连接,函数返回一个新的socket,新的socket用于与客户端通信,listen的socket只负责监听客户端的连接请求。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值