TCP/IP网络编程

TCP/IP网络编程

  • 基础知识
    • 网络编程
      • 编写使两台连网地计算机互相交互数据的程序。
    • 套接字
      • 网络数据传输用的软件设备,一个工具。
    • 知识补给
      • linux文件描述符
        • 就是给文件标个号,是个int型,0,1,2分别给了标准输入,标准输出,标准错误输出,也就说程序里要是在来一个,是从3开始。
      • 以_t为后缀的数据类型
        • 操作系统定义的数据类型,给本来的数据类型套了个壳子,不同计算机系统切换修改方便(比如32位到64位)。
  • linux
    • 接电话套接字
      • #include <sys/socket.h>
      • socket()
        • 创建套接字
        • int socket(int domain, int type, int protocol);
          • 成功返回文件描述符,失败返回-1
      • bind()
        • 分配IP地址和端口号
        • int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);
          • 成功返回0,失败返回-1
      • listen()
        • 转为可接受请求状态
        • int listen(int sockfd, int backlog);
          • 成功返回0,失败返回-1
      • accept()
        • 受理连接请求
        • int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
          • 成功返回文件描述符,失败返回-1
        • 注:
          • 等待,直到遇到连接请求之后返回。
    • 打电话套接字
      • #include <sys/socket.h>
      • socket()
        • 创建套接字
        • int socket(int domain, int type, int protcol);
          • 成功返回文件描述符,失败返回-1
      • connect()
        • 向服务器端发送连接请求
        • int connect(int sockfd, struct sockaddr *serv_addr, socklen_t addrlen);
          • 只有在两种情况下改函数才会返回
            • 服务器接收连接请求
            • 发生断网等异常情况而中断连接,或者服务器没在可连接状态,就没连上。
    • 文件操作
      • open()
        • #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>
        • int open(const char *path, int flag)
          • 成功返回文件描述符,失败返回-1
        • 参数解释
          • path
            • 文件名的字符串地址
          • flag
            • 文件的打开模式
      • close()
        • #include <uistd.h>
        • int close(int fd)
          • 成功返回0,失败返回-1
      • write()
        • #include <unistd.h>
        • ssize_t write(int fd, const void* buf, size_t nbytes);
          • 成功返回写入的字节数,失败返回-1
        • 参数解释
          • buf
            • 需要write的数据地址
          • nbytes
            • 需要传输的字节数
      • read()
        • #include <unistd.h>
        • ssize_t read(int fd, void* buf, size_t nbytes);
          • 成功返回读取的字节数,失败返回-1
  • windows
    • 初始化winsock相关库
      • #include <winsock2.h>
        • 链接报错找不到符号:-lwsock32
      • int WSAStartup(WORD, wVersionRequested, LPWSADATA lpWSAData);
      • wVersionRequested
        • winsock版本
          • MAKEWORD(1, 2):1.2版本,该函数的返回值:0x0201(低八位是主版本号)。
          • MAKEWORD(2, 2):2.2版本,该函数的返回值:0x0202。
      • IpWSAData
        • WSADATA结构体变量的地址值
      • 成功返回0,失败非0值
    • 注销winsock相关库
      • int WSACleanup(void)
      • 成功时返回0,失败返回SOCKET_ERROR
    • 套接字相关函数
      • #include <winsock2.h>
      • socket()
        • 创建套接字
        • SOCKET socket(int af, int type, int protocol);
          • 成功返回套接字句柄,失败返回INVAID_SOCKET
      • bind()
        • 分配IP地址和端口号
        • int bind(SOCKET s, const struct sockaddr *name, int namelen);
          • 成功时返回0,失败返回SOCKET_ERROR
      • listen()
        • 转为可接受请求状态
        • int listen(SOCKET s, int backlog);
          • 成功时返回0,失败返回SOCKET_ERROR
      • accept()
        • 受理连接请求
        • SOCKET accept(SOCKET s, struct sockaddr *addr, int *addrlen);
          • 成功返回套接字句柄,失败返回INVAID_SOCKET
      • connect()
        • 向服务器端发送连接请求
        • int connect(SOCKET s, const struct sockaddr *name, int namelen);
          • 成功时返回0,失败返回SOCKET_ERROR
      • closesocket()
        • 关闭套接字
        • closesocket(SOCKET s);
          • 成功时返回0,失败返回SOCKET_ERROR
    • winsock数据传输函数
      • send()
        • #include <winsock2.h>
        • int send(SOCKET s, const char* buf, int len, int flags);
          • 成功返回发送字节数,失败返回SOCKET_ERROR
      • recv()
        • #include <winsock2.h>
        • int recv(SOCKET s, const char* buf, int len, inf flags);
          • 成功返回接收字节数(收到EOF时是0),失败返回SOCKET_ERROR
    • winsock编程公式
      • int main(int argc, char* argv[]) {     WSADATA wsaData;     ....     if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)     {         ErrorHandling("WSAStartup() error!");     }     ....     return 0; }
  • 创建套接字
    • int socket(int domain, int type, int protocol);
      • 成功返回文件描述符,失败返回-1
    • 函数解释
      • domain
        • 协议族信息(Protocol Family)
        • PF_INET
          • IPv4互联网协议族
        • PF_INET6
          • IPv6互联网协议族
      • type
        • 套接字类型,套接字的数据传输方式
        • SOCK_STREAM:面向连接的套接字
          • 可靠的,按序传递的,基于字节的面向连接的数据传输方式的套接字。
          • 注:传输的数据不存在边界,也就是说你发你的,我收我的,收的不管发的发了几次,我就收就行。
        • SOCK_DGRAM:面向消息的套接字
          • 不可靠的,不按序传递的,以数据的高速传输为目的的套接字。
      • protocol
        • 最终确定用那个协议,就是说有些情况下,前面两个参数不能唯一确定一个协议,就需要用到这个协议,如果前面两个参数确定了,那这个参数可以给0。
        • IPv4里面向连接的协议,只有TCP,IPPROTO_TCP,面向消息的协议只有UDP,IPPROTO_UDP。
  • IP地址和端口号分配
    • IP地址和端口号
      • IP地址
        • 发给那个机器?
      • 端口号
        • 发给机器的那个软件?
        • 端口号是同一操作系统内区分不同的套接字。
        • 编码
          • 端口号编码长度是16位,则范围是:0~65535;0~1023是知名端口号,则使用时应该从1024开始。
        • 是否能重复
          • TCP套接字和UDP套接字不会共用端口号,所以可以重复用一个端口号。
    • 字节序转换
      • #include <arpa/inet.h>
      • unsigned short htons(unsigned short);
      • unsigned short ntohs(unsigned short);
      • unsigned long htons(unsigned long);
      • unsigned long ntohs(unsigned long);
      • 解释
        • n
          • network,网络序。
        • h
          • host,主机序,因为arm,x86都是小端,这里平时可以就认为是小端。
        • s
          • short
        • l
          • long
    • IP地址字符串和网络字节序转换
      • #include <arpa/inet.h>
      • 字符串转网络序整型,不会自动把变量存入sockaddr_in
        • inet_addr()
          • int_addr_t inet_add(const char * string);
          • 成功返回32位网络序整型值,失败返回INADDR_NONE
      • 字符串转网络序整型,会自动把变量存入sockaddr_in
        • inet_aton()
          • int inet_aton(const char * string, struct in_addr * addr)
          • 成功时返回1, 失败返回0
      • 网络序整型转字符串
        • inet_ntoa()
          • char * inet_ntoa(struct in_addr  addr)
          • 注:
            • 该函数的返回值是自动存储类型,也就说他只在该函数下一次执行之前存在,再次执行会覆盖。
    • 初始化struct sockaddr_in
      • 定义
        • struct sockaddr_in {     sa_family_t      sin_family;  // 协议族     uint16_t         sin_port;    // 端口号     struct in_addr   sin_addr;    // 32ip地址     char             sin_zero[8]; // 不使用,只是占位置,为了大小和struct sockaddr对齐 };  struct in_addr {     in_addr_t        s_addr;      // 32位ipv4地址 };
        • 注:
          • sin_port和sin_addr都是以网络字节序保存数据的。
        • struct sockaddr {     sa_family_t      sin_family;  // 协议族     char             sa_data[14]; // 地址信息:14 =  端口号的2 + ip地址的4 + sin_zero[8] };
        • 注:
          • 不知道sockaddr为了啥,但一定是为了啥搞了这种设计。以至于给sockaddr赋值很麻烦,所有搞了sockaddr_in作为中间给sockaddr赋值。
      • 初始化
        • 就是给sockaddr_in赋值
        • struct sockaddr_in addr; char * serv_ip = "10.146.4.47"; char * serv_port = "9190"; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(serv_ip); addr.sin_port = htons(atoi(serv_port));
          • struct sockaddr_in addr; char * serv_port = "9190"; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY) addr.sin_port = htons(atoi(serv_port));
          • INADDR_ANY
            • 自动获取主机IP地址,还是整型的
            • 服务器优先使用这种方式,客户端除非带有一部分服务器的功能,否则别用。(这不废话吗,客户端你得告诉程序你要访问那个服务器啊?你不说我那知道)
    • 服务器分配IP地址和端口号:bind()
      • 给创建的套接字,分配IP地址和端口号
      • int bind(int sockfd, struct sockaddr* myaddr, socklen_t addrlen);
        • 成功返回0,失败返回-1
      • 函数解释
        • sockfd
          • 文件描述符,这里其实就是你创建的套接字。
        • myaddr
          • sockaddr结构体,记录协议族,ip和端口号;
          • 实际使用中是先声明myaddr为struct sockaddr_in,然后在转化为sockaddr*,直接向sockaddr赋值会有问题。
            • struct sockaddr_in serv_addr 。。。。 (struct sockaddr*) &serv_addr
        • addrlen
          • sockaddr结构体的大小
      • 注:
        • 这个函数其实,就是在给你创建的套接字的这个结构体赋值。
    • 客户端分配IP地址和端口号:connect()
      • 提供服务器的IP地址和端口号,向服务器端发送连接请求
      • int connect(int sockfd, struct sockaddr *serv_addr, socklen_t addrlen);
        • 客户端的IP地址和端口号是啥时候分配的呢?是在connect调用之后自动分配的。
    • windows
      • 头文件
        • #include <winsock2.h>
      • 没有inet_aton()函数
      • 有俩windows独有的ip网络序整型和字符串的转换,同时支持IPV4和IPV6,但是因为跨系统扩展不好,一般不用。
  • TCP套接字编程
    • TCP服务器端
      • 函数调用顺序
        • socket()
          • 创建套接字
        • bind()
          • 分配套接字地址
        • listen()
          • 等待连接状态
        • accept()
          • 处理连接请求
        • read()/write()
          • 数据交换
        • close()
          • 断开连接
      • listen()
        • 进入等待连接状态
        • int listen(int sockfd, int backlog);
          • 成功返回0,失败返回-1
        • 参数解释
          • backlog
            • 连接请求等待队列的大小,单位是连接请求的个数。
        • 注:
          • listen()相当与干了两件事情,一是让服务器套接字进入等待连接状态,二是决定了连接请求队列的大小。
      • accept()
        • 受理客户端连接请求
        • int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
          • 成功返回文件描述符,失败返回-1
        • 参数解释
          • 返回值
            • 等待,直到遇到连接请求之后返回,也就是遇到connet()之后返回。
            • 返回值是和客户端连接之后的套接字描述符,也就是用于数据传输的套接字描述符。
          • sockfd
            • 之前创建的服务器套接字
          • struct sockaddr *addr
            • struct sockaddr *addr存的是发起请求的客户端的套接字地址,是客户端的,是通过connect()传过来的。
          •  socklen_t *addrlen
            • 是个指针,指向sockaddr的size(是个数);
            • 实际编码:
              • socklen_t clietAddrSize = sizeof(sockaddr); linkSock = accept(serverSock, (struct sockaddr*)&clientAddr, &clietAddrSize);
          • 刚开始创建的套接字只是为了做一些准备工作,accept()创建的套接字才是最重要的,才是用于数据传输的套接字。
    • TCP客户端
      • 函数调用顺序
        • socket()
          • 创建套接字
        • connect()
          • 请求连接
        • read()/write()
          • 数据交换
        • close()
          • 断开连接
      • connect()
        • 向服务器端发送连接请求
        • int connect(int sockfd, struct sockaddr *serv_addr, socklen_t addrlen);
          • 成功返回0,失败返回1
        • 参数解释
          • 返回值
            • 只有在两种情况下改函数才会返回
              • 服务器接收连接请求
              • 发生断网等异常情况而中断连接,或者服务器没在可连接状态,就没连上。
          • struct sockaddr *serv_addr
            • struct sockaddr *serv_addr是服务器端的套接字地址,客户端的套接字地址是在调用connect()之后自动分配的。
          • connect()运行成功之后,并不意味着服务器端就会调用accept,服务器会先把连接请求放到等待队列,也就是说connect()返回之后并不会立即进行数据交换。
          • accept()运行之后会返回新的服务器端套接字,用于进行数据传输,但是connet()不是,他就是让原本的客户端套接字和服务器端套接字连接上了。
    • 迭代回声服务器端和客户端
      • 服务器端处理多个客户端的请求,实现就是循环执行accept(),数据交换和close()的部分。
      • 服务器端和客户端进行多次数据交互,实现就是循环数据交互的部分。
      • 注:
        • TCP套接字编程数据传输的部分一定要注意一个事情,就是TCP的数据传输是没有边界的,write()发就是发,read()读就是读,他俩谁也不管谁。如果write()的数据过大,实际中是分两次或者多次发出去的,read()可不管,他就从buffer里读,所以他读的是第一次的,还是第一次+第二次的完全看运气。
        • 回声客户端的问题解决:协议,提前约定数据的大小。
  • TCP原理
    • TCP套接字I/O缓冲
      •  I/O缓冲每个套接字都有自己的,关闭套接字输出缓冲不会丢失,但是输入缓冲会丢失。(很容易理解,因为输出缓冲已经从套接字发出去了,输入缓冲是要发给套接字的。)
      • TCP不存在超过缓冲区大小的情况,因为TCP会控制流数据,控制流的大小。
    • TCP工作原理
      • 建立连接
        • 三次握手
          • 简单说,就是在数据交换前,两个套接字有三次交流。“我连接你OK不?”,“OK的”,“好的”。
          • A:"我俩做好朋友好不?"
          • B:"可以啊!"
          • A:"好的,收到,我们是好朋友了"
      • 数据交换
        • 套接字是全双工的
        • SEQ&ACK
          • SEQ
            • 序号
            • 就是给你发的序号
          • ACK
            • 确认号
            • 你发我的我已收到,我下一个想要的序号。
          • ACK值
            • ACK = SEQ + 我接收成功的字节数 + 1
            • 这样就能准确的反馈,你发我的我收到了没,收到了多少,我下一个想要的是啥。
        • 传输错误
          • 如果发送端迟迟未收到接收端的反馈,就会重传。
          • 如果收到了反馈,接收端收到了,但是没收全,是重传上一次的整个,还是只传丢失的?
      • 断开连接
        • 四次握手
          • 简单说,就是断开连接前,两个套接字会进行四次交流
          • A:我想断开连接
          • B:是吗?请稍等
          • B:我准备好了,可以断开连接
          • A:收到,那拜拜
    • 注:
      • 整个TCP的工作模式简单说,就是“句句有回应,事事有反馈,卿卿我我,恩恩爱爱。”
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值