<计算机网络自顶向下> TCP&UDP套接字编程

应用实现:源端的应用进程交换报文实现应用协议,来实现各种各样的网络应用(dash,email, etc)

而应用层通信不可以直接通信,需要借助下层的服务才可以进行,通过层间接口交给下层,通过下层的服务传输

传输层在TCP/IP提供的就是socket API服务,传输报文之前建立socket,借助于socket收发,使用完成了以后关掉(socket就像一个门,接和收都一样)

两种socket类型:

  • TCP:可靠的,字节流/管道(报文之间没有边界)服务
  • UDP:不可靠(数据UDP数据包)服务

TCP套接字编程

  • 服务器首先运行,等待连接建立

|---------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1. 创建欢迎socket:目的是返回一个整数(这个整数就是welcome soket,但没什么具体含义) 2. 将这个创建的整数和本地端口捆绑 3. 在欢迎socket上阻塞式等待接受用户的连接 (调用accept函数,如果没连接就阻塞,反之接受连接) 总的来说:创建------捆绑------等待 |

  • 客户端主动和服务器建立连接

    |--------------------------------------------------------------------------|
    | 1. 创建客户端本地套接字(隐式捆绑到本地端口,就是默认与当前没用的端口捆绑) 2. 调用connect阻塞连接指定服务器进程的IP地址和端口号 |

  • 当与客户端连接请求到来时

    |----------------------------------------------------------------------------|
    | 1. 服务器接受来自用户端的请求,解除阻塞式等待,返回一个新的sockket值(同时与服务端和客户端捆绑,这个是connection socket) |

  • 连接API调用有效时,客户端与服务器建立了TCP链接

数据结构 sockaddr_in

  • 作用:指定网络操作的目标地址(设置网络进程和端口号)
  • 不仅仅可以用于IP通信,还可以用于IPX通信。所以要给一个常量说明使用在哪个通信的
  • IP地址和port捆绑关系的数据结构(标示进程的端节点)
  • 这个结构体用于存储 IP 地址。它包含了一个地址族(通常是 AF_INET,表示 IPv4),一个端口号和一个 IP 地址。当你创建一个套接字并想要连接到一个特定的 IP 地址和端口号时,你需要设置一个 struct sockaddr_in 结构体,并将它传递给 connect 或 bind 函数
cpp 复制代码
//IP地址和port捆绑关系的数据结构(标示进程的端节点)
struct sockaddr_in{
 short sin_family;    //AF_INET 
 u_short sin_port;   // port
 struct in_addrsin_addr; 
//  IP address, unsigned long
 char sin_zero[8]; // align
 }; 

数据结构 host_ent

  • 作用:获取主机信息(IP地址)
  • 更清晰的表述:struct hostent:这个结构体用于存储主机的信息,如主机名、别名、地址类型、地址长度和地址列表。当你调用 gethostbyname 函数时,它会返回一个指向 struct hostent 类型的指针。这个结构体包含了你所查询的主机的信息。例如,如果你查询的是一个域名,那么 gethostbyname 函数会返回这个域名对应的 IP 地址
  • 包括
    • *h_name:主机域名的指针

    • **h_aliases:主机的一系列别名,二级指针

    • h_length:地址长度

    • 第四个是IP地址的列表,二级指针

      cpp 复制代码
      //域名和IP地址的数据结构
      struct hostent
       { char *h_name; 
      char **h_aliases; 
      int h_addrtype; 
      int h_length; /*地址长度*/ 
      char **h_addr_list; 
      #define h_addr h_addr_list[0];
       } 
      //作为调用域名解析函数时的参数
      //返回后,将IP地址拷贝到sockaddr_in的IP地址部分

C/S socket交互:TCP

|---------------------------------------------------------------------------------------------------|
| |

代码示例

客户端

也就是说这个sockaddr实际存储的是即将连接的服务器信息以及联机方式,而gethostbyname实际上是提供映射函数,只要输出服务器端口号就可以得到一个hostent指针然后用箭头函数得出需要的信息比如IP地址等等然后copy到socketaddr。

注意!这里socket是隐式的绑定了本地可用端口,没有显示调用bind函数

cpp 复制代码
/*client.c*/ 
void main(int argc, char *argv[])
 { 
struct sockaddr_in sad; /* structure to hold an IP address of server */ 
    //定义一个套接字,用于与服务器通信
int clientSocket; /* socket descriptor */ 
    //定义一个主机信息结构体指针,用于存储主机的信息
struct hostent* ptrh; /* pointer to a host table entry  */ 
    //定义一个字符数组,用于存储要发送给服务器的消息
char Sentence[128]; 
    //定义一个字符数组,用于存储从服务器接收的消息
char modifiedSentence[128]; 
    // argv[0]是执行的程序的名字; atoi转化为整型
host = argv[1]; port = atoi(argv[2]);
    /*创建socket,参数指明internet地址族,PF_INET:这是第一个参数,表示协议族(Protocol             
    Family)或地址族(Address Family)。PF_INET 表示使用 IPv4 网络协议。
    SOCK_STREAM:这是第二个参数,表示套接字类型(Socket Type)。SOCK_STREAM 表示使用面向连接的 
    TCP 协议。
    0:这是第三个参数,表示使用默认的协议。对于 SOCK_STREAM 类型的套接字,默认的协议就是 TCP。*/
clientSocket= socket(PF_INET, SOCK_STREAM, 0); 
//从下一行开始,所有缩进的代码都是为了创建连接socket然后和服务器连接
        //之前有一个sad变量,sad是个结构体变量,分配给内存,先清零
        //这是因为新声明的变量在内存中可能会有一些旧的、无用的数据,这可能会干扰我们后续的操作
    memset((char *)&sad,0,sizeof(sad)); /* clear sockaddr structure */ 
        //对sad赋值Internet地址族
    sad.sin_family= AF_INET; /* set family to Internet */ 
        //将port强制转化为无符号短整型
        /*htons 是一个在网络编程中常用的函数,全称是 "host to network short"。这个函数的作用是                        
        将一个16位的数从主机字节序转换为网络字节序。

        在网络通信中,为了保证数据的正确传输,我们需要统一数据的字节顺序。网络字节序通常是大端字节                
        序(Big-Endian),也就是最重要的字节(Most Significant Byte,MSB)存储在内存的最低地址        
        处。

        主机字节序则取决于你的机器,有的机器是大端字节序,有的机器是小端字节序(Least         
        Significant Byte,LSB,存储在内存的最低地址处)。因此,在发送数据之前,我们通常会使用         
        htons 函数将数据从主机字节序转换为网络字节序。

        例如,如果你的机器是小端字节序,那么一个16位的数 0x1234 在内存中的存储顺序是 34 12。使用 
        htons 函数后,这个数在内存中的存储顺序就会变为 12 34,符合网络字节序。

        总的来说,htons 函数的作用就是确保数据在网络上传输时,不同的机器能够正确地解析数据*/
    sad.sin_port= htons((u_short)port);
        /*host主机域名。ptrh为结构体指针,gethostbyname就是调用解析器 
        获取主机的信息,包括主机的IP地址
        在这段代码中,ptrh 是一个指向 struct hostent 类型的指针。struct hostent 是一个结构            
        体,它包含了主机的信息,如主机名、别名、地址类型、地址长度和地址列表*/
    ptrh= gethostbyname(host); 
        /* 将主机的IP地址复制到服务器地址结构体的 sin_addr 字段 */
    memcpy(&sad.sin_addr, ptrh->h_addr, ptrh->h_length); 
        //将IP地址拷贝到sad.sin_addr
    connect(clientSocket, (structsockaddr*)&sad, sizeof(sad));
  //从用户处得到输入流
gets(Sentence);
  //发送给服务器用户先前输入的东西
n=write(clientSocket, Sentence, strlen(Sentence)+1); 
  //读取服务器端发过来的转换完毕的字符
n=read(clientSocket, modifiedSentence, sizeof(modifiedSentence));
printf("FROM SERVER: %s\n",modifiedSentence); 
  //关闭连接
close(clientSocket); 
}

服务器

cpp 复制代码
/* server.c  */
// argc参数数量, argv里面的内容是服务器端守候的端口号,只有一个参数
 void main(int argc, char *argv[])
 { 
//sad是作为服务器本地的端节点结构体变量,cad放的是client的端节点的结构体变量
struct sockaddr_in sad; /* structure to hold an IP address  of server*/ 
struct sockaddr_in cad; /*client  */
//welcome socket仅仅有自己的端口号,和对方的无关
int welcomeSocket, connectionSocket; /* socket descriptor */ 
struct hostent *ptrh; /* pointer to a host table entry */ 
//存储客户端发来的信息
char clientSentence[128]; 
//处理客户端发过来的东西后的结果
char capitalizedSentence[128]; 
//端口号
port = atoi(argv[1]);
//建立并bind welcome_socket
welcomeSocket = socket(PF_INET, SOCK_STREAM, 0);   
    memset((char *)&sad,0,sizeof(sad)); /* clear sockaddr structure */ 
    sad.sin_family = AF_INET; /* set family to Internet */ 
        //INADDR_ANY代表本地任何一个IP地址
    sad.sin_addr.s_addr = INADDR_ANY; /* set the local IP address */ 
        //格式转化(同客户端)
    sad.sin_port = htons((u_short)port);/* set the port number */ 
bind(welcomeSocket, (struct sockaddr *)&sad, sizeof(sad));
/* Specify the maximum number of clients that can be queued */
 //在服务的时候又来了请求,然后这些请求放在这个长度为10的队列,如果超过队列就拒绝
 listen(welcomeSocket, 10)
  //服务器进入无限循环,不断接收和处理客户端的连接请求
 while(1) { 
     //等待用户连接建立请求
     connectionSocket=accept(welcomeSocket, (structsockaddr*)&cad, &alen);
     //读取并写入发过来的东西
     n=read(connectionSocket, clientSentence, sizeof(clientSentence));
     /* capitalize Sentence and store the result in capitalizedSentence*/
     n=write(connectionSocket, capitalizedSentence, strlen(capitalizedSentence)+1); 
     close(connectionSocket);  
    } 
}

UDP套接字编程

  • 没有握手
  • UDP socket仅仅和本地的IP和端口相关,发送端需要在发送的时候显式的指定对方的IP地址和端口
  • 服务器必须从收到的分组中提取出发送端的IP地址和端口号
  • 传送的数据可能乱序,也可能丢失
  • 无连接的数据单元也叫数据报,所以没办法通过"数据报"这个名字判断TCP还是UDP

C/S交互:UDP

  • recvfrom阻塞,知道对方发来消息

代码示例

客户端

cpp 复制代码
 /* client.c  */
 void main(int argc, char *argv[])
{ 
struct sockaddr_in sad; /* structure to hold an IP address */ 
int clientSocket; /* socket descriptor */ 
struct hostent *ptrh; /* pointer to a host table entry */ 
char Sentence[128]; 
char modifiedSentence[128]; 
host = argv[1]; port = atoi(argv[2]);
clientSocket = socket(PF_INET, SOCK_DGRAM, 0); 
/* determine the server's address */
memset((char *)&sad,0,sizeof(sad)); /* clear sockaddr structure */ 
sad.sin_family = AF_INET; /* set family to Internet */ 
sad.sin_port = htons((u_short)port); 
ptrh = gethostbyname(host);
 /* Convert host name to IP address */
memcpy(&sad.sin_addr, ptrh->h_addr, ptrh->h_length);  
gets(Sentence);
addr_len =sizeof(struct sockaddr); 
n=sendto(clientSocket, Sentence, strlen(Sentence)+1,
(struct sockaddr *) &sad, addr_len); 
n=recvfrom(clientSocket, modifiedSentence, sizeof(modifiedSentence),
(struct sockaddr *) &sad, &addr_len);
printf("FROM SERVER: %s\n",modifiedSentence); 
close(clientSocket);  
}

服务器

cpp 复制代码
 /* server.c  */
 void main(int argc, char *argv[])
 { 
struct sockaddr_in sad; /* structure to hold an IP address */ 
struct sockaddr_in cad;
int serverSocket; /* socket descriptor */ 
struct hostent *ptrh; /* pointer to a host table entry */ 
char clientSentence[128]; 
char capitalizedSentence[128]; 
port = atoi(argv[1]);
serverSocket = socket(PF_INET, SOCK_DGRAM, 0);   
memset((char *)&sad,0,sizeof(sad)); /* clear sockaddr structure */ 
sad.sin_family = AF_INET; /* set family to Internet */ 
sad.sin_addr.s_addr = INADDR_ANY; /* set the local IP address */ 
sad.sin_port = htons((u_short)port);/* set the port number */ 
bind(serverSocket, (struct sockaddr *)&sad, sizeof(sad));
while(1) { 
 n=recvfrom(serverSocket, clientSentence, sizeof(clientSentence), 0
 (struct sockaddr *) &cad, &addr_len );
 /* capitalize Sentence and store the result in capitalizedSentence*/
 n=sendto(serverSocket , capitalizedSentence, strlen(capitalizedSentence)+1,
 (struct sockaddr *) &cad, &addr_len); 
 } 
}
相关推荐
久绊A1 小时前
网络信息系统的整个生命周期
网络
_PowerShell1 小时前
[ DOS 命令基础 3 ] DOS 命令详解-文件操作相关命令
网络·dos命令入门到精通·dos命令基础·dos命令之文件操作命令详解·文件复制命令详解·文件对比命令详解·文件删除命令详解·文件查找命令详解
_.Switch3 小时前
高级Python自动化运维:容器安全与网络策略的深度解析
运维·网络·python·安全·自动化·devops
qq_254674413 小时前
工作流初始错误 泛微提交流程提示_泛微协同办公平台E-cology8.0版本后台维护手册(11)–系统参数设置
网络
JokerSZ.4 小时前
【基于LSM的ELF文件安全模块设计】参考
运维·网络·安全
UestcXiye4 小时前
《TCP/IP网络编程》学习笔记 | Chapter 3:地址族与数据序列
c++·计算机网络·ip·tcp
qq_421833675 小时前
计算机网络——SDN
计算机网络
小松学前端6 小时前
第六章 7.0 LinkList
java·开发语言·网络
城南vision6 小时前
计算机网络——TCP篇
网络·tcp/ip·计算机网络