网络编程之socket学习笔记

English Version: https://today2tmr.com/en/2017/06/29/network-programming-socket-learning-note/

由于初学,目的导向为理解,笔记将偏向于浅显而缺乏深度。

基本概念

  • 我们通常用IP和端口号去定位一个网络服务。
  • socket就想是电话线的两头,一旦电话接通,通讯就开始了。
  • 有三种形式的sockets,我们讨论其中的两种:
    • 流式套接字 SOCK_STREAM(TCP)
    • 数据包套接字 SOCK_DGRAM (UDP)

TCP与UDP的对比

  • 流式套接字 SOCK_STREAM(TCP)
    • 面向连接,数据正确性高,效率较低。
    • 服务端与客户端建立一对一连接,每次信息传递均有正确性校验,保证数据准确到达目的地。
  • 数据包套接字 SOCK_DGRAM (UDP)
    • 无连接,数据可能丢失,高效率。
    • 服务端和客户端均只需1个套接字。就像快递站,填写邮寄信息即可向任一地点发货。
    • 如语音、视频通话同网络通讯通常采用UDP以获得高效传播速率,偶尔的数据丢失也不会造成大的影响。

三次握手和四次挥手

即TCP的建立连接和断开连接。

建立连接

  • [Shake 1] 套接字A:“你好,套接字B,我这里有数据要传送给你,建立连接吧。”
  • [Shake 2] 套接字B:“好的,我这边已准备就绪。”
  • [Shake 3] 套接字A:“谢谢你受理我的请求。”

note:

  • SYN:Synchronous,标志位,建立一个新连接。
  • Seq:Sequence Number,对数据包的标记序号。
  • ACK:Acknowledge,确认序号有效。
  • Ack:Acknowledge Number,确认号,其值等于Seq + 1。

断开连接

  • [Shake 1] 套接字A:“任务处理完毕,我希望断开连接。”
  • [Shake 2] 套接字B:“哦,是吗?请稍等,我准备一下。”
  • 等待片刻后……
  • [Shake 3] 套接字B:“我准备好了,可以断开连接了。”
  • [Shake 4] 套接字A:“好的,谢谢合作。”

note:

  • FIN:Finish,断开一个连接。
  • Seq:Sequence Number,对数据包的标记序号。
  • ACK:Acknowledge,确认序号有效。
  • Ack:Acknowledge Number,确认号,其值等于Seq + 1。

相关函数(C++,TCP)

  • TCP函数应用图
    TCP

创建套接字

函数原型

  • Linux:
    • int socket(int family, int type, int protocol);
  • Windows:
    • SOCKET socket(int family, int type, int protocol)

参数解释与取值

  • family: Address Family, IP地址类型
    • AF_INET: IPv4因特网域
    • AF_INET6: IPv6因特网域
    • (部分)
  • type: 数据传输方式
    • SOCK_STREAM
    • SOCK_DGRAM
    • (部分)
  • protocol: 传输协议
    • 0: 取决于type
    • IPPROTO_TCP: TCP
    • IPPROTO_UDP: UDP
    • (部分)

note:

  • 有时可以通过family和type推算出传输协议,这种情况下即可将protocol设为0。

绑定套接字

函数原型

  • Linux:
    • int bind(int sock, struct sockaddr *addr, socklen_t addrlen);
  • Windows:
    • int bind(SOCKET sock, const struct sockaddr *addr, int addrlen);

参数解释与取值

  • sock: 即socket函数返回值
  • addr: 指向sockaddr结构体变量的指针
  • addrlen: addr的长度,可由sizeof()计算

返回值:

  • 0: 成功
  • -1: 出错

note:

  • 我们通常创建并赋值sockaddr_in(用于IPv4,IPv6用sockaddr_in6)结构体变量,并将其转换为sockaddr结构体变量。

sockaddr结构体

sockaddr_in结构体

note:
  • in_addr_t在头文件<netinet/in.h>中定义,等价于unsigned long,需用inet_addr()函数将IP字符串转化。
  • sin_port需要经过htons()的转换。

sockaddr_in6结构体

实例


 

连接服务器

函数原型

  • Linux:
    • int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen);
  • Windows:
    • int connect(SOCKET sock, const struct sockaddr *serv_addr, int addrlen);

参数解释与取值

与bind()相同。

监听和接受

函数原型

监听:

  • Linux:
    • int listen(int sock, int backlog);
  • Windows:
    • int listen(SOCKET sock, int backlog);

接受:

  • Linux:
    • int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);
  • Windows:
    • SOCKET accept(SOCKET sock, struct sockaddr *addr, int *addrlen);

参数解释与取值

  • sock/addr/addrlen: 同上
  • backlog: 请求队列的最大长度

监听返回值:

  • 0: 成功
  • -1: 出错

接受返回值:

  • 一个新的套接字描述符。
  • -1: 出错

note:

  • addr是客户端的信息,sock是服务端的信息
  • 后面和客户端通信时,要使用新返回的套接字

接收和发送数据

函数原型

发送:

  • Linux:
    • ssize_t write(int fd, const void *buf, size_t nbytes);
  • Windows:
    • int send(SOCKET sock, const char *buf, int len, int flags);

接收:

  • Linux:
    • ssize_t read(int fd, void *buf, size_t nbytes);
  • Windows:
    • int recv(SOCKET sock, char *buf, int len, int flags);

参数解释与取值

  • fd: 文件描述符,也就是socket
  • buf: 数据保存的地址
  • nbytes/len: 传递的数据的字节数
  • flags: 一般为0/NULL

发送返回值:

  • 写入的字节数
  • -1: 错误

接收返回值:

  • 接收的字节数
  • 0: 另一端套接字已关闭
  • -1: 错误

note:

  • ssize_t是signed size_t之意,即符号数

关闭套接字

函数原型

  • Linux:
    • close(int sock)
  • Windows:
    • closesocket(SOCKET sock)

相关函数(C++,UDP)

  • UDP函数应用图
    UDP

与TCP相同的部分

  • 创建套接字
  • 绑定套接字
  • 关闭套接字

与TCP不同的部分

接收

  • Linux:
    • ssize_t recvfrom(int sock, void *buf, size_t nbytes, int flags, struct sockadr *from, socklen_t *addrlen);
  • Windows:
    • int recvfrom(SOCKET sock, char *buf, int nbytes, int flags, const struct sockaddr *from, int *addrlen);

发送

  • Linux:
    • ssize_t sendto(int sock, void *buf, size_t nbytes, int flags, struct sockaddr *to, socklen_t addrlen);
  • Windows:
    • int sendto(SOCKET sock, const char *buf, int nbytes, int flags, const struct sockadr *to, int addrlen);

note:

  • 由于没有连接的存在,不会使用到connect,accept和listen函数。

Windows和Linux下的编程区别

以下差异均为前者为Windows下,后者为Linux下。

  • 头文件: winsock.h/winsock2.h vs. sys/socket.h
  • 套接字类型: SOCKET vs. int
  • 读写函数: recv()/send() vs. read()/write()
  • 关闭套接字: closesocket() vs. close()

其它:

  • Windows下,PE_INET等同于AF_INET,即Protocol Family。
  • Windows下,需要初始化动态链接库,WSAStartup。

关于WSAStartup()(Windows下)

  • 加载DLL: #pragma comment (lib, "ws2_32.lib")
  • 初始化DLL:
  • 终止DLL: WSACleanup();

简单例子

收发文本信息

http://c.biancheng.net/cpp/html/3039.html

收发文件

http://c.biancheng.net/cpp/html/3045.html

学习资料

Leave a Reply

Your email address will not be published.