TCP/IP网络编程笔记

一、基础

1.1 套接字定义

套接字是网络数据传输的软件设备。我们可以通过套接字完成数据传输。

1.2 相关函数

网络编程中接受连接请求的套接字创建过程如下:

  1. 调用socket函数创建套接字
  2. 调用bind函数分配IP和端口号
  3. 调用listen函数转换为可接受请求状态
  4. 调用accept受理连接请求
  • Linux平台相关函数
    #include <sys/socket.h>
    int socket(int domain, int type, int protocol);
         成功返回文件描述符,失败返回-1
    int bind(int socketfd, struct sockadr* myaddr, socklen_t addrlen);
    int listen(int socketfd, int backlog);
    int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen);
    int connect(int sockdf, struct sockaddr* serv_addr, socklen_t addrlen);
         成功返回0,失败返回-1
    
  • Windows平台相关函数
    #include <winsock2.h>
    //需要链接ws2_32.lib
      //初始化
    int WSAStartup(WORD wVersionRequested,LPWSADATA lpwSAData);
            成功返回0,失败返回错误代码
            wVersionRequested    使用的Winsock版本信息
            lpwSAData                   WSADATA结构体变量的地址
    MAKEWORD(1,2);  //宏函数,主版本为1,副版本2,返回0x0201
    //注销
    int WSACleanup();
       成功返回0,失败返回SOCKET_ERROR
    //套接字
    SOCKET socket(int af, int type, int protocol);
       成功时返回套接字句柄,失败返回INVALID_SOCKET
    int bind(SOCKET s, const struct sockaddr* name, int namelen);
       成功返回0,失败返回SOCKET_ERROR
    int  listen(SOCKET s, int backlog);
       成功返回0,失败返回SOCKET_ERROR
    SOCKET accept(SOCKET s, struct sockaddr* addr, int* addrlen);
       成功时返回套接字句柄,失败返回INVALID_SOCKET
    int connect(SOCKET s, const struct sockaddr* name, int namelen);
       成功返回0,失败返回SOCKET_ERROR
    int closesocket(SOCKET s);
       成功返回0,失败返回SOCKET_ERROR
    

1.3 类型和设置

1.3.1 协议(Protocol)

  • 协议族分类
    • PF_INET IPV4协议族
    • PF_INET6 IPV6协议族
    • PF_LOCAL 本地通信的Unix协议族
    • PF_PACKET 底层套接字的协议族
    • PF_IPX IPX Novell协议族
      通过套接字的第一个参数传递使用的协议分类信息,domain决定使用的protocol。
  • 套接字类型
    类型指的是套接字的数据传输方式,决定了协议族不能决定数据传输方式,如PF_INET可能存在多种数据传输方式。
    • SOCK_STREAM(面向连接的套接字)
      面向连接的套接字具有以下特点:

      • 传输过程中数据不会消失
      • 按序传输数据
      • 传输的数据不存在数据边界

      收发数据的内部有缓冲(字节数组)。套接字传输的数据保存到该数组。在数组容量内,可能在充满缓冲后一次读取,也可能分多次读取。如果缓冲被填满,传输套接字将停止传输。

    • SOCK_DGRAM(面向消息的套接字)
      面向消息的套接字特点: 不可靠的、不按序传递、以数据的高速传递为目的的套接字。

1.4 地址族和字节序

1.4.1 地址族

IP是网络协议的简写,是为了收发网络数据而分配给计算机的值。只要有IP就能像数据发送到目标计算机,但无法发送给最终的应用程序。
端口号是为了区分程序中创建的套接字而分配给套接字的序号。NIC通过IP向计算机内部发生数据,操作系统利用端口号将数据分配给套接字。
IP地址分为IPV4(4字节)和IPV6(16字节)两类。IPV6是为了解决201年前后IP地址耗尽而提出的标准。IPV4分为A、B、C、D四类,由网络ID和主机主机ID构成。 IP网络数据传输时,先基于网络地址(网络ID)把数据发送到网络。网络收到数据后,根据主机ID将数据发送到目标计算机。
地址分类

  • A类地址首字节 0-127,首位以0开始
  • B类地址首字节128-191,首位以10开始
  • C类地址首字节192-223,首位以110开始

端口号

  • 端口号由16位构成,可分配端口号为0-65535
  • 知名端口号:0-1023。一般分配给特定程序。

1.4.2 地址信息表示

struct sockaddr_in
{
    sa_family_t       sin_family;  //地址族
    uint16_t          sin_port;     //16位端口号
    struct in_addr    sin_addr;  //32位IP地址
    char              sin_zero[8];  //不使用
}

in_addr结构体声明为uint32_t,只需要当作32位整数。
sin_zero是使结构体与socd_addr保持一致而插入的成员。
之所以使用sockaddr_in,而不直接使用sockaddr,原因在于sockaddr结构体,如下:

struct sock_addr
{
    sa_family_t sin_family;
    char sa_data[14];
}

sa_data中包含地址和端口号,其余部分为0。直接填充这些信息会比较麻烦,于是有了sockaddr_in,最后转换为sockaddr型结构体。

1.4.3 字节序

大端序: 高位字节放在地位地址
小端序: 高位字节放在高位地址
在通过网络传输数据时约定统一方式,即网络字节序。网络字节序是大端序
字节序转换

unsigned short htons(unsigned short);
unsigned short ntohs(unsigned short);
unsigned long htonl(unsigned long);
unsigned long ntohl(unsigned long);
//h--->host字节序
//n-->network字节序
//s-->2字节short,常用于端口号转换
//l-->4字节,常用于IP转换

地址字符串的字节序转换

#include <arpa/inet.h>

in_addr_t inet_addr(const char* string);  //点分十进制字符串---> 大端序整数值

int inet_aton(const char* string,  struct in_addr* addr);  //string转换结果保存到addr地址值中

char* inet_ntoa(struct in_addr adr);  //转换为字符串形式。长期保存时,需要对char*内容进行复制

网络地址初始化

struct sockaddr_in addr;
char* server_ip = "211.217.168.11"; //ip字符串
char* serv_port = "9190";  //端口号

memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(server_ip);
addr.sin_port = htons(server_port);

INADDR_ANY
利用INADDR_ANY分配Ip地址,将自动获取计算机的地址,若计算机有多个IP地址,则可以从不同IP、相同端口号中接收数据。服务器一般采用这种方式。

1.5 域名和网络地址

将容易记、易表达的域名分配并取代IP地址。域名是赋予服务器端的虚拟地址,而非实际地址。通过DNS请求转换地址。若DNS服务器无法解析,会询问其他DNS服务器,并提供给用户。

//
#include <netdb.h>

struct hostent* gethostbyname(const char* hostname);

structhostent* gethostbyaddr(const char* addr, socklen_t len, int family);

1.6套接字 多种可选项

1.6.1 可选项

套接字可选项是分层的。IPPROTO_IP是IP协议相关选项,IPPROTO_TCP是tcp协议相关可选项,SOL_SOCKET是套接字相关通用可选项。
设置可选项

#include <sys/socket.h>

int getsockopt(int sock, int level, int optname, void* optval, socklen_t* optlen);
     sock 套接字文件描述符
     level 协议层
     optname 可选项名字
     optval    保持查看结果的缓冲地址
     optlen   缓冲大小

int setsockopt(int sock, int level, int optname, const void* optval, socklen_t optlen);

SO_TYPE 查看套接字类型
SO_SNDBUF/SO_RCVBUF 输入输出缓冲区大小
SO_REUSEADDR :默认为0(假),即无法分配Time-wait状态下的套接字端口号, 为1(真)可将Time-wait状态下的套接字端口号重新分配给新的套接字
套接字四次挥手后并非立即清除,二十经过一段时间的Time-wait状态。先断开连接(发送FIN消息)的主机才经过Time-wait状态。因此服务器先断开连接,无法立即重新运行。
TCP_NODEALY:设置为1时,禁用Nagle算法。
tcp套接字默认使用Nagle算法(只有收到前一数据的ACK时,才发送下一数据)交换数据,因此最大限度的进行缓冲,直到收到ACK。为了提高网络传输效率,必须使用Nagle算法。但在网络流量未受到太大影响时,不使用Nagle算法会更快。如大文件数据。

1.7 进程

1.8 IO复用与IO函数

无论连接多少客户端,提供服务的进程只有一个。select是最具代表性的服务端复用方法。

1.9 线程

1.10 信号处理

二、TCP

2.1 定义

IP层解决数据传输中的路径选择问题。TCP和UDP利用IP层提供的路径信息完成实际的数据传输。

2.2 服务端

TCP服务端函数调用通常遵循如下顺序:

  1. socket -- 创建套接字
  2. bind -- 分配套接字地址
  3. listen -- 等待连接请求状态
  4. accept -- 允许连接
  5. read/wrote -- 数据交换
  6. close -- 断开连接

listen后,若有新的连接请求,应该按序处理。受理请求意味着进入可接受数据的状态。
accept受理连接请求。其内部产生用于数据IO的套接字,并返回其文件描述符。
客户端通过connect发起连接请求,当服务端接收连接请求或者发生异常中断连接请求时返回。服务端接收连接请求只是将请求记录加入等待队列,并不表示调用accept,不表示立即进行数据交换。

2.3 迭代服务端

通过插入循环语句,反复调用accept函数来持续受理客户端连接请求的方式。

2.4 迭代回声服务/客户端

TCP数据传输不存在边界。服务端通过1次write函数传输数据,但是如果数据太大,可能会分成多个数据包发送。另外,客户端也可以未完全收到数据就read。解决方法是提前确定接收数据的大小。在无法预知数据大小的情况下,需要应用层协议的定义。收发数据的过程中定义号规则以表示数据的边界。

2.5 半关闭

IO缓冲特性:

  • I/O缓冲在每个套接字中单独存在
  • 在创建套接字时自动生成
  • 关闭套接字后,也会继续传输输出缓冲中的遗留数据
  • 关闭套接字,会丢失输入缓冲中的数据

Linux的close和Windows的closescket意味着完全断开连接,即无法传输和接受数据。这样可能存在主机A发送数据后断开连接,无法继续接收B的数据。因此,Half-close产生,即只关闭1个流。

半关闭函数

#include <sys/socket.h>

int shutdown(int sock, int howto);
     //howto为断开方式信息

断开方式如下:

  • SHUT_RD 断开输入流
  • SHUT_WR 断开输出流
  • SHUT_RWDR 同时断开输入输出流

shutdown进行半关闭,同时发送EOF。

2.6 并发服务器

2.7 IO分离

2.8 select服务端

2.8.1 select调用过程

  1. 设置文件描述符、指定监视范围、设置超时
  2. 调用select函数
  3. 查看调用结果

2.9 多线程服务端

三、 UDP

3.1 定义

UDP提供不可靠数据传输。UDP不提供流控制机制。根据端口号将传到主机的数据交付给最终的UDP套接字。
TCP比UDP慢主要由于:

  • 收发数据前后进行的连接设置和清楚操作
  • 收发数据过程中为保证可靠性而添加的流控制

3.2 UDP服务端/客户端

3.2.1 UDP数据IO函数

#include <sys/cosket.h>

ssize_t sendto(int sock, void* buf, size_t bnytes, int flags,  struct sockaddr* to, socklen_t addrlen)

ssize_t recvfrom(int sock, void* buf, size_t nbytes, int flags, struct sockaddr* from, socklen_t* addrlen)

3.2.2 套接字地址分配

sendto前完成地址分配。如果sendto时未分配地址信息,首次调用时自动分配IP和端口号,分配结果保留到程序结束。IP为主机IP,端口号为任意未分配端口。
此外,也可以用bind分配ip和端口号。

3.3 数据传输特性和connect

存在数据边界。通过snedto传输数据过程如下:

  1. 向套接字注册目标IP和端口号
  2. 传输数据
  3. 删除注册信息
    未注册目标信息的套接字称为未连接套接字。反之称为connected套接字。
    向同一目标主机进行长时间通信时,将UDP套接字变为connected套接字会提高效率。传输过程中,1和3占传输的1/3时间。

3.4 多播与广播

四、Windows平台

4.1 异步IO

4.2 重叠IO

4.3 IOCP

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容