第二章、套接字类型与协议设置

本文详细介绍了套接字的两种主要类型:面向连接的套接字(如TCP)和面向消息的套接字(如UDP),以及它们在数据传输特性和应用场景上的区别。面向连接的套接字提供可靠、按序的数据传输,而面向消息的套接字则强调速度,但可能丢失或损坏数据。文中还提供了在Linux和Windows平台上创建和使用这两种套接字的示例代码。
摘要由CSDN通过智能技术生成

第二章、套接字类型与协议设置

2、1 套接字协议及其数据传输特性

  • 协议(Protocol): 为完成数据交换而定好的约定。

  • 创建套接字:

    • #include <sys/socket.h>
      int socket(int domain, int type, int protocol);// 成功时返回文件描述符,失败返回-1.
      
      • domain : 套接字使用的协议族(Protocol family)信息。
      • type : 套接字数据传输类型信息。
      • protocol : 计算机通信使用的协议信息
  • 协议族(protocol family):

    • 套接字通信中的协议有一些分类,这些分类信息称为协议族。socket函数的第一个参数传递套接字中使用的协议分类信息。
    • 书中重点介绍PF_INET(IPv4互联网协议簇),套接字实际采用的最终协议是由socket函数第三个参数传递的。
  • 套接字类型(type):

    • 指套接字的数据传输方式,通过socket函数第二个参数传递。只有指明了类型才能决定套接字数据传输方式。
  • 套接字类型1:面向连接的套接字(SOCK_STREAM)

    • 向socket函数第二个参数传递SOCK_STREAM将创建面向连接的套接字。
      • 面向连接的套接字数据传输特征:
        • 传输过程中数据不会消失。
        • 按序传输数据。
        • 传输的数据不存在数据边界。
        • 套接字连接必须一一对应。
      • 收发数据的套接字内部由缓冲(buffer)即字节数组,通过套接字传输的数据将保存到该数组。面向连接的套接字中,read函数和write函数的调用次数并无太大意义,所以说不存在数据边界。
      • 当缓冲被填满后,套接字无法接收数据,此时传输端将停止传输。
      • 面向连接的套接字会根据接收端的状态传输数据,传输出错还会重传。所以不会发生数据丢失。
      • 面向连接的套接字只能与另一个同样特征的套接字连接。
    • 可靠的、按序传递的、基于字节的面向连接的数据传输方式的套接字。
  • 套接字类型2:面向消息的套接字(SOCK_DGRAM)

    • 向socket函数第二个参数传递SOCK_DGRAM将创建面向消息的套接字。
    • 面向消息的套接字数据传输特征:
      • 强调快速传输而非传输顺序
      • 传输的数据可能丢失也可能损毁
      • 传输的数据有数据边界
      • 限制每次传输的数据大小
    • 面向消息的套接字比面向连接的套接字具有更快的传输速度,但是无法避免数据丢失或损毁,每次传输的数据大小具有一定的限制,并存在数据边界(意味着接收数据次数应和传输次数相同)
    • 不可靠的、不按序传递的、以数据的高速传输为目的的套接字。
    • 不存在连接的概念。
  • 协议的最终选择:

    • socket函数的第三个参数决定最终采用的协议。前两个参数传递了 协议族信息套接字数据传输方式 即可创建所需套接字,大部分情况下可以向第三个参数传递0, 除非 同一个协议族中存在多个数据传输方式相同的协议
    • 数据传输方式相同,但协议不同,此时需要第三个参数具体指定协议信息。
  • 例子:

    • “IPv4协议族中面向连接的套接字”

      • int tcp_socket = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);// TCP套接字
        
    • “IPv4协议族中面向消息的套接字”

      • int udp_socket = socket(PF_INET,SOCK_DGRAM,IPPROTO_UDP); // UDP套接字
        
  • 面向连接的套接字:TCP套接字示例:

    • 验证TCP套接字的特性:传输的数据不存在数据边界。

    • 为验证此,需要使write函数的调用次数不同于read函数的调用次数。在客户端中多次调用read函数以接收服务器端发送的全部数据。

    • 服务器端 tcp_server.c

    • #include <stdio.h>
      #include <stdlib.h>
      #include <string.h>
      #include <sys/socket.h>
      #include <unistd.h>
      #include <arpa/inet.h>
      void error_handling(char*message);
      
      int main(int argc,char *argv[]){
      
          int serv_sock;
          int clnt_sock;
      
          // 定义在头文件 <arpa/inet.h>中,定义了Inernet socket address
          struct sockaddr_in serv_addr;
          struct sockaddr_in clnt_addr;
          socklen_t clnt_addr_size;
      
          char message[] = "Hello World!";
      
          if(argc != 2){
              printf("Usage : %s <port>\n",argv[0]);
              exit(1);
          }
      
          // PF_INET ,IP协议簇,#define PF_INET 2
          // SOCK_STREAM 有序的、可信赖的、基于连接的字节流
          // 调用socket函数生成 服务器socket套接字
          serv_sock = socket(PF_INET,SOCK_STREAM,0);
          // socket函数成功返回文件描述符,失败返回-1
          if(serv_sock == -1)
              error_handling("socket() error");
          
          // sin_family 端口号,#define AF_INET PF_INET
          memset(&serv_addr,0,sizeof(serv_addr));
          serv_addr.sin_family = AF_INET;
          serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
          serv_addr.sin_port = htons(atoi(argv[1]));
      
          // bind函数将创建好的套接字分配IP地址和端口号
          if(bind(serv_sock,(struct sockaddr*) &serv_addr,sizeof(serv_addr)) == -1)
              error_handling("listen() error");
          // listen函数将套接字转化为可接受连接状态
          if(listen(serv_sock,5) == -1)
              error_handling("listen() error");
          
          clnt_addr_size = sizeof(clnt_addr);
          // accept函数受理连接请求,如果在没有连接请求的情况下调用该函数,则不会返回,直到有连接请求为止
          clnt_sock= accept(serv_sock,(struct sockaddr*)&clnt_addr,&clnt_addr_size);
          if (clnt_sock == -1)
              error_handling("accept() error");
          //稍后要将介绍的 write 函数用于传输数据,若程序经过 accept 这一行执行到本行,则说明已经有了连接请求
          write(clnt_sock, message, sizeof(message));
          close(clnt_sock);
          close(serv_sock);
          return 0;
      }
      
      void error_handling(char *message)
      {
          // 将字符串写入 stderr 流中
          fputs(message, stderr);
          // 将字符写入erroe stream中
          fputc('\n', stderr);
          exit(1);
      }
      
    • 客户端 tcp_client.c

    • #include <stdio.h>
      #include <stdlib.h>
      #include <string.h>
      #include <unistd.h>
      #include <arpa/inet.h>
      #include <sys/socket.h>
      void error_handing(char *message);
      
      int main(int argc,char*argv[]){
          int sock;
          struct sockaddr_in serv_addr;
          char message[30];
          int str_len = 0;
          int idx = 0, read_len = 0;
      
          if(argc!= 3){
              printf("Usage : %s <IP> <port>\n",argv[0]);
              exit(1);
          }
      
          // 创建 IPv4协议族中面向连接的套接字, 创建TCP套接字,前两个参数是PF_INET、SOCK_STREAM时,可以省略IPPROTO_TCP
          sock = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
          if(sock == -1)
              error_handing("socket() error");
      
          memset(&serv_addr, 0, sizeof(serv_addr));
          serv_addr.sin_family = AF_INET;
          serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
          serv_addr.sin_port = htons(atoi(argv[2]));
      
          // connect函数向服务器发送连接请求
          if(connect(sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr)) == -1)
              error_handing("connect() error");
      
          // read函数读取数据,成功时返回接收的字节数,失败返回-1
          // 反复调用read函数,每次读取1字节,read返回0,说明遇到了文件结尾,
          while(read_len=read(sock,&message[idx++],1)){
              if(read_len == -1)
                  error_handing("read() error");
              // 累加read调用接收到的信息的字节数。
              str_len += read_len;
          }
      
          printf("Message from server : %s \n", message);
          // 由于一次读取一个字节,所以传递了多少数据说明调用了多少此read
          printf("Function read call count : %d \n",str_len);
          close(sock);
          return 0;
      }
      
      void error_handing(char *message){
          fputs(message,stderr);
          fputc('\n',stderr);
          exit(1);
      }
      
    • 在这里插入图片描述

    • 可以看到服务器端发送了13字节的数据,客户端调用了13次read函数进行读取。

2、2 Windows平台下的实现与验证

  • windows系统的socket函数

    • #include <winsock2.h>
      SOCKET socket(int af, int type, int protocol); // 成功返回socket句柄,失败返回INVALID_SOCKET
      
    • socket函数返回类型是SOCKET,结构体用来保存整数型套接字句柄值。失败时返回的INVALID_SOCKET可以理解为一个常数。

  • 基于Windows的TCP套接字示例:

    • 服务器端

      • #include <stdio.h>
        #include <stdlib.h>
        #include <WinSock2.h>
        #pragma comment (lib, "ws2_32.lib")  //加载 ws2_32.dll
        void ErrorHanding(const char* message);
        
        int main(int argc, char* argv[]) {
        	WSADATA wsaData;
        	SOCKET hServSock, hClntSock;
        	SOCKADDR_IN servAddr, clntAddr;
        
        	int szClntAddr;
        	char message[] = "Hello World!";
        	if (argc != 2) {
        		printf("Usage: %s <port>\n", argv[0]);
        		exit(1);
        	}
        
        	// 成功时返回0,失败时返回非0的错误值代码,设置程序中用到的Winsock版本
        	// 初始化套接字库
        	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
        		ErrorHanding("WSAStartup() error");
        	// 调用socket函数生成 服务器socket套接字, 成功时返回套接字句柄,失败返回INVALID_SOCKET
        	// 创建套接字
        	hServSock = socket(PF_INET, SOCK_STREAM, 0);
        	if (hServSock == INVALID_SOCKET)
        		ErrorHanding("socket() error");
        
        	memset(&servAddr, 0, sizeof(servAddr));
        	servAddr.sin_family = AF_INET;
        	servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
        	servAddr.sin_port = htons(atoi(argv[1]));
        
        	// 调用 bind 函数分配ip地址和端口号
        	if (bind(hServSock, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
        		ErrorHanding("bind() error");
        
        	// listen函数将套接字转化为可接受连接状态,使其可接收客户端连接。
        	// 称为服务器端套接字
        	if (listen(hServSock, 5) == SOCKET_ERROR)
        		ErrorHanding("Listen() error");
        
        	szClntAddr = sizeof(clntAddr);
        	// accept函数受理连接请求,如果在没有连接请求的情况下调用该函数,则不会返回,直到有连接请求为止
        	// accept 函数受理客户端请求
        	hClntSock = accept(hServSock, (SOCKADDR*)&clntAddr, &szClntAddr);
        	if (hClntSock == INVALID_SOCKET)
        		ErrorHanding("accept() error");
        
        	// send()函数向客户端传输数据
        	send(hClntSock, message, sizeof(message), 0);
        	closesocket(hClntSock);
        	closesocket(hServSock);
        	// 程序终止前注销套接字库
        	WSACleanup();
        	return 0;
        
        }
        
        void ErrorHanding(const char* message) {
        	fputs(message, stderr);
        	fputc('\n', stderr);
        	exit(1);
        }
        
    • 客户端

      • #include <stdio.h>
        #include <stdlib.h>
        #include <WinSock2.h>
        #pragma comment (lib, "ws2_32.lib")  //加载 ws2_32.dll
        void ErrorHanding(const char* message);
        
        
        int main(int argc, char* argv[]) {
        	WSADATA wsaData;
        	SOCKET hSocket;
        	SOCKADDR_IN servAddr;
        
        	char message[30];
        	int strlen = 0;
        
        	int idx = 0, readlen = 0;
        	if (argc != 3) {
        		printf("Usage: %s <IP> <port>\n", argv[0]);
        		exit(1);
        	}
        	// 初始化Winsock库
        	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
        		ErrorHanding("WSAStartup() error!");
        	// 创建套接字
        	hSocket = socket(PF_INET, SOCK_STREAM, 0);
        	if (hSocket == INVALID_SOCKET)
        		ErrorHanding("socket() error");
        
        	memset(&servAddr, 0, sizeof(servAddr));
        	servAddr.sin_family = AF_INET;
        	servAddr.sin_addr.s_addr = inet_addr(argv[1]);
        	servAddr.sin_port = htons(atoi(argv[2]));
        
        	// 向服务端发送连接请求
        	if (connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
        		ErrorHanding("connect() error");
        
        	// recv函数接收服务器端发送来的数据
        	while (readlen = recv(hSocket, &message[idx++], 1, 0)) {
        		if (readlen == -1)
        			ErrorHanding("read() error");
        		strlen += readlen;
        	}
        	printf("Message from serve : %s \n", message);
        	printf("Function read call count : %d \n", strlen);
        
        	closesocket(hSocket);
        	WSACleanup();
        	return 0;
        
        }
        
        void ErrorHanding(const char* message) {
        	fputs("message", stderr);
        	fputc('\n', stderr);
        	exit(1);
        }
        
    • 结果:

      • 在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值