APUE笔记---网络IPC:socket套接字使用+聊天程序

来源:互联网 发布:淘宝企业店铺客服电话 编辑:程序博客网 时间:2024/05/18 03:37

APUE笔记—网络IPC:socket套接字使用+聊天程序

1. 套接字描述符的创建与销毁

套接字是通信端点的抽象,套接字描述符是一种文件描述符。

1.1创建套接字描述符

#include <sys/types.h>      #include <sys/socket.h>int socket(int domain, int type, int protocol);//返回值:若成功,返回文件(套接字)描述符,若出错,返回-1
  • 参数domain(域)确定通信特性,包括地址格式。下图列出部分:

|域|描述|
|:—:|:—:|
|AF_INET|IPv4因特网域|
|AF_INET6|IPv6因特网域|
|AF_UNIX,AF_LOCAL|UNIX域|

  • 参数type 确定套接字的类型,进一步确定通信特征。

|类型|描述|协议|
|:—:|:—:|:—:|
|SOCK_STREAM|有序、可靠、双向、面向连接的字节流|TCP|
|SOCK_DGRAM|固定长度、无连接、不可靠的报文传递|UDP|
|SOCK_SEQPACKET|固定长度、有序、可靠、面向连接的报文传递|SCTP|
|SOCK_RAW|原始套接字|自行构造协议头部|

  • 参数protocol通常为0,表示给定的域和套接字类型选择默认协议。

1.2销毁套接字

套接字通信是双向的,可以采用shutdown函数禁止一个套接字的IO

#include <sys/socket.h>int shutdown(int sockfd, int how);//返回值:若成功,返回0;若出错,返回-1.
  • 参数how有三种选项

|how|描述|
|:—:|:—:|
|SHUT_RD|关闭读端|
|SHUT_WR|关闭写端|
|SHUT_RDWR|关闭读写|

shutdown函数和close函数的区别

  • close函数用于关闭一个文件描述符,只是将文件描述符的引用计数减1,当引用计数减为0,则释放该文件描述符,否则引用该文件描述符的进程正常使用。
  • shutdown函数用于禁止一个套接字的IO,无论它的文件描述符是否为0,都会设定套接字处于设定状态,并且引用该文件描述符的进程受影响。

2. 寻址

网络进程标示由两部分组成,并且唯一确定

  1. 计算机的网络地址,也就是IP地址。
  2. 计算机的端口号。

2.1字节序

在不同的处理器的存放方式主要有两种,以内存中0x0A0B0C0D的存放方式为例,分别有以下几种方式:
大端序
数据以8bit为单位:

地址增长方向 →
| … | 0x0A | 0x0B | 0x0C | 0x0D| …|

示例中,最高位字节是0x0A 存储在最低的内存地址处。下一个字节0x0B存在后面的地址处。
小端序
数据以8bit为单位:

地址增长方向 →
| … | 0x0D | 0x0C | 0x0B | 0x0A | … |
最低位字节是0x0D 存储在最低的内存地址处。后面字节依次存在后面的地址处。

TCP/IP协议栈使用大端序,所以在网络传输之前要进行字节序转换
有四个用来在处理器字节序和网络字节序之间实施转换的函数

#include <arpa/inet.h>uint32_t htonl(uint32_t hostlong);//返回值:以网络字节序标示的32位整数uint16_t htons(uint16_t hostshort);//返回值:以网络字节序标示的16位整数uint32_t ntohl(uint32_t netlong);//返回值:以主机字节序标示的32位整数uint16_t ntohs(uint16_t netshort);//返回值:以主机字节序标示的16位整数
  • h表示“主机”字节序, n表示“网络”字节序。
  • l表示“长”整形,4字节,32位;s表示“短”整形,16位。

2.2地址格式

一个地址标识一个特定通信域的套接字端点,地址格与这个特定的通信相关,为使不同的地址格式传入套接字函数,地址会被强行转换成一个通用的地址结构sockaddr;

struct sockaddr {        sa_family_t     sa_family;      /* address family, AF_xxx       */        char            sa_data[14];    /* 14 bytes of protocol address */};//在IPv4因特网域(AF_INET)中,套接字地址用结构sockaddr_in表示struct sockaddr_in {  __kernel_sa_family_t  sin_family;     /* Address family               */  __be16                sin_port;       /* Port number                  */  struct in_addr        sin_addr;       /* Internet address             */  /* Pad to size of `struct sockaddr'. */  unsigned char         __pad[__SOCK_SIZE__ - sizeof(short int) - sizeof(unsigned short int) - sizeof(struct in_addr)];};/* Internet address             */struct in_addr {        __be32  s_addr;};//在IPv6因特网域(AF_INET6)中,套接字地址用结构sockaddr_in6表示struct sockaddr_in6 {        unsigned short int      sin6_family;    /* AF_INET6 */        __be16                  sin6_port;      /* Transport layer port # */        __be32                  sin6_flowinfo;  /* IPv6 flow information */        struct in6_addr         sin6_addr;      /* IPv6 address */        __u32                   sin6_scope_id;  /* scope id (new in RFC2553)*/};struct in6_addr {        union {                __u8            u6_addr8[16];#if __UAPI_DEF_IN6_ADDR_ALT                __be16          u6_addr16[8];                __be32          u6_addr32[4];#endif        } in6_u; //这个联合体大小是128字节,但是通过不同的分类,当读取ip地址时,会有不同的读取方式。#define s6_addr                 in6_u.u6_addr8#if __UAPI_DEF_IN6_ADDR_ALT#define s6_addr16               in6_u.u6_addr16#define s6_addr32               in6_u.u6_addr32#endif};

二进制地址格式与点分式十进制字符表示(a.b.c.d)之间相互转换,函数inet_ntop和inet_pton同时适用于IPv4和IPv6地址。

#include <arpa/inet.h>const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);//返回值:若成功,返回地址字符串指针,若出错,返回NULLint inet_pton(int af, const char *src, void *dst);//返回值:若成功,返回1,若格式无效,返回0,若出错,返回-1
  • 参数af可以是AF_INET,也可以是AF_INET6,若以不被支持的地址族作为af参数,则两个函数返回错误,并且设置errno为EAFNOSUPPORT
  • inet_pton函数将文本字符串格式转换成网络字节序的二进制地址。尝试转换src指向的字符串,并通过dst指针保存二进制结果。
  • inet_ntop函数将网络字节序的二进制地址转换为文本字符串格式。从数值格式(stc)转换成表达式格式(dst)。size是目标存储单元的大小,以免缓冲区溢出。

2.3 将套接字与地址关联

使用bind函数关联地址和套接字。

#include <sys/types.h>          /* See NOTES */#include <sys/socket.h>int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);//返回值:若成功,返回0;若出错,返回-1
  • bind将addr所指向的socket地址分配给未命名的sockfd文件描述符,addrlen参数指出socket地址的长度。
  • 在进程正在运行的计算机上,指定的地址必须有效,不能指定一个其他机器的地址
  • 地址必须和创建套接字时的地址组所支持的格式相匹配。
  • 地址中国的端口号必须不小于1024,除非该进程有相应的特权。
  • 一般只能将一个套接字端点绑定到一个给定的地址上。
    对于因特网域,如果IP地址为INADDR_ANY,套接字端点可以被绑定到所有系统网络接口上。这意味可以接收这个系统所安装的任何一个网卡的数据包。

3. 建立连接

3.1 使用connect函数来建立连接

#include <sys/types.h>          /* See NOTES */#include <sys/socket.h>int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);//返回值:若成功,返回0;若出错,返回-1

在connect中指定的地址是要通信的目标地址。如果sockfd没有绑定地址,connect会给调用者一个默认的地址。

3.2 服务器调用listen函数来接收连接请求

#include <sys/types.h>          /* See NOTES */#include <sys/socket.h>int listen(int sockfd, int backlog);//返回值:若成功,返回0;若出错,返回-1
  • 参数backlog提供一个提示,提示系统该进程要入队的未完成连接请求的数量。默认值为128,由

3.3 accept函数获得连接请求并建立连接

#include <sys/types.h>          /* See NOTES */#include <sys/socket.h>int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);//返回值:若成功,返回文件(套接字)描述符,若出错,返回-1
  • 函数accept返回的文件描述符是套接字描述符,该描述符连接到调用connect的客户端。该套接字描述符和原始套接字具有相同的套接字类型和地址族。
  • 若不关心客户端标识,可以将参数addr和len设为NULL。否则在调用accept之前,将addr参数设为足够大的缓冲区来存放地址,并且将len指向的整数设为这个缓冲区的字节大小。返回时,accept会在缓冲区填充客户端的地址,并且更新指向len的整数来反应该地址的大小。
  • 如果没有连接请求,accept会处于阻塞状态等待请求到来

4. 数据传输

对文件的读写操作read和write同样适用于socket。但是socket编程接口提供了几个专门用于socket数据读写的系统调用,增加了对数据读写的控制,用于TCP流数据读写的系统调用:

#include <sys/types.h>#include <sys/socket.h>ssize_t recv(int sockfd, void *buf, size_t len, int flags);//返回值:返回数据的字节长度,若无可用数据或对等方已经按序结束,返回0,如出错,返回-1。ssize_t send(int sockfd, const void *buf, size_t len, int flags);//返回值:若成功,返回发送的字节数,如出错,返回-1

5. 带外数据

  • 带外数据(out-of-band-data)是一些通信协议所支持的可选功能,与普通数据相比,它允许更高优先级的数据传输,带外数据先行传输即使传输队列已经有数据。TCP支持带外数据,但UDP不支持。
  • TCP将带外数据称为紧急数据(urgent data)。TCP仅仅支持一个字节的紧急数据,,但是允许紧急数据在普通数据传递机制数据流之外传输。
  • 用过send函数的的flags参数指定一个MSG_OOB标志可以产生紧急数据,如果带外数据超过一个字节,则视最后一个字节为紧急数据。

6.客户端和服务器端相互发送数据的简单实现

6.1服务器端程序代码

#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <sys/socket.h>#include <sys/types.h>#include <string.h>#include <arpa/inet.h>int main(int argc, char *argv[]){        struct sockaddr_in clientaddr, serveraddr;        int sockfd;        int connfd;        int addrlen;        //1.创建socket文件描述符用于处理监听        sockfd = socket(AF_INET, SOCK_STREAM, 0);        //2.初始化服务器端的地址族,端口,和IP地址        bzero(&serveraddr, sizeof(serveraddr));        serveraddr.sin_family = AF_INET;        serveraddr.sin_port = htons(atoi(argv[2]));        inet_pton(AF_INET, argv[1], &serveraddr.sin_addr);        //3.将sockfd文件描述符和服务器地址绑定        bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));        //4.监听,系统默认队列为128        listen(sockfd, 128);        while(1){                //5.接受客户端连接,返回一个connfd文件描述符用与处理连接                addrlen = sizeof(clientaddr);                connfd = accept(sockfd, (struct sockaddr *)&clientaddr, &addrlen);                int ret;                do{                        //6.相应请求,先接受客户发送信息并答应,然后发送信息                        char buf[1024];                        memset(buf, '\0', sizeof(buf));                        ret = recv(connfd, buf, sizeof(buf), 0);                        write(STDOUT_FILENO, buf, strlen(buf));                        char dst[128];                        printf("from ip:%s\tport:%d\n", inet_ntop(AF_INET, &clientaddr.sin_addr.s_addr, dst, sizeof(dst)), ntohs(clientaddr.sin_port));                        printf("*-------------------------------------*\n");                        memset(buf, '\0', sizeof(buf));                        read(STDOUT_FILENO, buf, sizeof(buf));                        send(connfd, buf, strlen(buf), 0);                        printf("msg is sent\n");                        printf("*-------------------------------------*\n");                }while(ret);                //7.关闭该用于连接的文件描述符                close(connfd);        }    //8.关闭用于监听的文件描述符        close(sockfd);        return 0;}

6.2客户端程序代码

#include <stdio.h>#include <sys/socket.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <arpa/inet.h>int main(int argc, char *argv[]){        struct sockaddr_in serveraddr;        int sockfd;        //1.创建用于连接服务器的sockfd        sockfd = socket(AF_INET, SOCK_STREAM, 0);        //2.初始化服务器端的地址信息,主机序转换为网络序        bzero(&serveraddr, sizeof(serveraddr));        serveraddr.sin_family = AF_INET;        serveraddr.sin_port = htons(atoi(argv[2]));        inet_pton(AF_INET, argv[1], &serveraddr.sin_addr);        //3.连接服务器        connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));        while(1){                //4.先发送,等待服务器端接受然后打印接受信息                char buf[1024] = { 0 };                read(STDOUT_FILENO, buf, sizeof(buf));                send(sockfd, buf, strlen(buf), 0);                printf("msg is sent\n");                printf("*-------------------------------------*\n");                //char buf[1024];                memset(buf, '\0', sizeof(buf));                recv(sockfd, buf, sizeof(buf), 0);                write(STDOUT_FILENO, buf, strlen(buf));                char dst[128];                printf("from ip:%s\tport:%d\n", inet_ntop(AF_INET, &serveraddr.sin_addr.s_addr, dst, sizeof(dst)), ntohs(serveraddr.sin_port));                printf("*-------------------------------------*\n");        }        //5.释放连接的文件描述符        close(sockfd);        return 0;}

6.3运行结果

这里写图片描述这里写图片描述

0 0
原创粉丝点击