TCP网络编程模型从入门到实战基础篇,单服务器单个用户非并发版本

简介: TCP网络编程模型从入门到实战基础篇,单服务器单个用户非并发版本

前言

  • 本文仅仅针对对于学校学习网络编程之后不知道如何运用的情况, 本章学习可以收获的是最为基本的 TCP 模型的掌握

编程模型图:

一、网络编程实践的必备基础知识

ip地址 (32位地址) :  网络地址 + 主机号 组成: 可以唯一标识一台主机。。。。 前面的网络地址, 是确定是不是同一个网段下的。。。。  同一个网段下, 区分不同的电脑, 使用就是ip地址。。。。。但是ip地址不一定是永远唯一确定的, ?????   基于网络连接的地址,  在不同的地方联网, 每一次重新联网, ip地址都是可能不一样的.....   核心在于ip 不是物理地址, 是需要连接网络的。。。。                                                                                                                      

mac地址: 相比上述 ip 地址, mac 地址 就是一个 永远唯一确定的物理地址, 相当于是你的身份证, 出场全球唯一确定的.....   因为  不是 像 ip  基于 不同的网段 网络连接会发生改变的....      

端口号(port) (16位) :唯一标识一个进程。。。。此处我们谈谈为何需要端口号。。。。。  如果在同一台主机上  两个进程间需要建立通信连接, 或者是  不同主机上不同的进程之间需要确定一个连接,这个时候 如果只有  ip 地址 是不够的, 因为无法唯一确定到一个 进程上去......                                                                                                        

网络连接建立的根本是为了什么, 本质是什么???      

任何的网络服务器与网络客户端, 如果要进行正常的数据通信, 必须使用端口号, 来进行唯一标识自身进程......

唯一标识进程  :     在同一个 OS 内,一个进程可以与一个端口号进行绑定, 该端口号就在网络层面唯一标识一主机上的唯一一个进程  

ip + port    :   标识全网内唯一的一个进程    

socket的理解 :   英文含义 : 插口, 我的简单理解, 在服务器端和客户端同时开一个口子, 然后数据的收发都是通过这个口子实现的, 这样理解一下, 感觉就像是弄一根虚拟的网线, 一端插在客户端,一端插在服务端,然后进行通信.......        开一个口子, 从口子里面读取数据, 往口子里面发送数据

创建socket的细节分析 :    IP层 (网络层) : 使用的是IPV4 还是 IPV6,   传输层使用的是 TCP 还是 UDP (本文仅仅研究 TCP)       针对   TCP  我们 使用的是   IPV4 (AF_INET) +   TCP (SOCK_STREAM)

数据传输之网络传输存储格式统一 :      机器存在大端机器小端机器的区别, 区分大端机器还是小端机器就是查看低地址存储的是低字节数据还是高字节数据, 低地址存储低字节数据 (小端存储), 低字节存储高字节数据 (大端存储). 正是由于不同的机器存储数据的格式不同, 但是我们如何正确的在网络上传输界定这些数据的存储格式呢????  

解决办法:     统一成固定的网络字节序: 网络字节序, 是按照大端存储的格式传输数据流的地址信息的, 所以如果是大端机器我们直接不需要转换数据格式直接可以传输, 小端机器就需要转换为网络字节序传入其中, 确定数据流地址信息

如下的代码注释帮助理解: h : host(主机) n :network(网络)  l :long (32位ip地址)  s : short (16位

  • port)  
SYNOPSIS
       #include <arpa/inet.h>
       uint32_t htonl(uint32_t hostlong);
       uint16_t htons(uint16_t hostshort);
       uint32_t ntohl(uint32_t netlong);
       uint16_t ntohs(uint16_t netshort);
DESCRIPTION
       The htonl() function converts the unsigned integer hostlong from host byte order to network byte order.
       The htons() function converts the unsigned short integer hostshort from host byte order to network byte order.
       The ntohl() function converts the unsigned integer netlong from network byte order to host byte order.
       The ntohs() function converts the unsigned short integer netshort from network byte order to host byte order.
       On  the  i386 the host byte order is Least Significant Byte first, whereas the network byte order, as used on the Internet, is Most Significant
       Byte first.

然后针对  sockaddr结构进行图解分析  

  • struct sockaddr_in 结构以及分析如下:    
  1. sin_family : 协议家族 (域)   直接写  AF_INET 表示 IPV4网络协议  
  2. sin_port    : 16位端口号   (注意字节序)
  3. sin_addr   :  32位 IP地址   (注意字节序)  

二、系统调用方法刨析

1.socket

returnval  :         On success, a file descriptor for the new socket is returned.  On error, -1 is returned, and errno is set appropriately.                成功返回套接字文件描述符, 失败返回 -1

2.bind

结构体具体参数设置刨析:

INADDR_ANY  :   本质是一个宏,可以代表本地的任意IP地址,因为服务器可能存在多个网卡, 每个网卡也可能绑定多个ip 地址, 这样设置可以在所有的ip地址上监听, 直到和某个客户端建立了连接时才确定下来到底用哪个ip地址.        (说白了  就是 通配 服务器上的任意可能的ip地址,  避免单机 多ip 造成的 ip 设置错误问题)


return val :   成功返回0,  失败返回 -1.

3.listen

return val :   成功 返回 0 失败返回 -1 且 传入的 sockfd 变成监听套接字.


监听套接字(listenfd) :  全局唯一一个监听套接字在服务器上, 特别注意, 监听套接字全局唯一一个, 一直保持监听状态, (不像后文中的 connfd : connectfd : 连接套接字)


连接套接字 (connfd)   :  每建立了一个连接就会从新产生一个新的  connfd。。。。


生活实例理解监听套接字 和连接套接字, 连接套接字也可叫做服务套接字:


比如说一个高档的酒店, 门口一直有一个接车欢迎的服务员, 这个服务员一直在门口进行监听工作, 来了客人就把人家请进去, 进去之后由单独的另外一个服务员对齐进行服务, 这个酒店是比较高档的, 进去之后服务员是一直对齐服务的, 每来一个人都需要一个新的服务套接字(连接套接字)对齐进行服务,直到离开, 至于门口的监听套接字, 是一直不断的监听哪个客户来了, 哪个要建立连接工作.......

4.accept

代码模型 :

while (1) {
        clientaddr_len = sizeof(clientaddr);
        connfd = accept(listenfd, (SA*) &clientaddr, &clientaddr_len);
        服务逻辑代码
        close(connfd);       //服务结束关闭连接..
}

return val :   返回值 正是上述的   connfd, 连接套接字, 每实现一个连接 都会返回一个新的connfd, 所以一般accept函数都是放在一个死循环中不停的接收客户端的连接亲求,并且对齐进行服务....

5.connect

return val :   成功返回0, 失败返回 -1

辅助函数:   后序 需要使用 字符串ip地址 和   sin_addr之间的相互转换:

三、实际案例代码

1.服务器端

代码如下:

[tangyujie@VM-4-9-centos Serve]$ cat server.c
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <strings.h>
#define SERVE_PORT 12345
#define ERR_EXIT(m) \
  do { perror(m); exit(EXIT_FAILURE); } while (0)
typedef struct sockaddr SA;
int listenfd;               //设置全局监听套接字, 方便关闭
void handle(int signo) {
  fprintf(stdout, "ByeBye!\n");
  close(listenfd);
  exit(EXIT_SUCCESS);
}
int main() {
  signal(SIGINT, handle);
  int connfd;
  struct sockaddr_in serveAdd, clientAdd;
  socklen_t clientAdd_len;
  char ipbuff[256];
  //创建套接字
  if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
    ERR_EXIT("socket"); //协议家族 服务类型(套接字类型), 协议弃用(0)
  }
  //确定服务端地址簇
  bzero(&serveAdd, sizeof(serveAdd));         //清0
  serveAdd.sin_family = AF_INET;
  serveAdd.sin_port = htons(SERVE_PORT);
  serveAdd.sin_addr.s_addr = htonl(INADDR_ANY);   //注意转网络字节序
  //bind 端口 地址信息
  if (bind(listenfd, (SA*)&serveAdd, sizeof(serveAdd)) == -1) {
    ERR_EXIT("bind");
  }
  //开始监听..
  if (listen(listenfd, 3) == -1) {
    ERR_EXIT("listen");
  }
  printf("Accepting connections..\n");
  //循环不断的接收客户的连接请求进行服务
  while (1) {
    clientAdd_len = sizeof(clientAdd);
    if ((connfd = accept(listenfd, (SA*)&clientAdd, &clientAdd_len)) == -1) {
      ERR_EXIT("accept");
    }
    printf("recieve connection from ip is %s and port is %d\n",
      inet_ntop(AF_INET, &clientAdd.sin_addr, ipbuff, sizeof(ipbuff)), 
      ntohs(clientAdd.sin_port));
    //服务
    while (1) {
      char buff[1024] = {0};
      int i;
      int n = read(connfd, buff, sizeof(buff));   //读数据
      if (n == -1) {
        ERR_EXIT("read");
      } 
      if (n == 0) {                 //说明客户端主动断开连接
        break;
      }
      //处理数据, 简单的小写字符转大写
      for (i = 0; i < n; ++i) {
        buff[i] = toupper(buff[i]);
      }
      //写回
      write(connfd, buff, n);
    }
    fprintf(stdout, "ip %s and port is %d interrupt connfd\n",
        ipbuff, ntohs(clientAdd.sin_port));
    close(connfd);                    //服务结束断开连接
  }
    return 0;
}

2.客户端

代码如下 :

[tangyujie@VM-4-9-centos Serve]$ cat client.c
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <signal.h>
#include <string.h>
#define SERVE_PORT 12345                //端口号
#define ERR_EXIT(m)\
  do { perror(m); exit(EXIT_FAILURE); } while (0)     //错误处理
typedef struct sockaddr SA;               
int sockfd;                       //设置全局, 方便关闭
void handle(int signo) {
  fprintf(stdout, "ByeBye!\n");
  close(sockfd);
  exit(EXIT_SUCCESS);
}
int main(int argc, char* argv[]) {
  if (argc != 2) {
    fprintf(stderr, "%s <ip>", argv[0]);
    close(EXIT_FAILURE);
  }
  signal(SIGINT, handle);
  struct sockaddr_in serveAdd;
  //创建套接字
  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
    ERR_EXIT("socket"); //协议家族 服务类型(套接字类型), 协议弃用(0)
  }
    //确定服务端地址簇
  bzero(&serveAdd, sizeof(serveAdd));         //清0
  serveAdd.sin_family = AF_INET;
  serveAdd.sin_port = htons(SERVE_PORT);
  //将传入的ip字符串转换为 sin_addr
  if (inet_pton(AF_INET, argv[1], &serveAdd.sin_addr) == -1) {
    ERR_EXIT("inet_ntop");
  } 
  //不需要绑定端口号 系统随机分配一个临时端口号, 直接连接
  if (connect(sockfd, (SA*)&serveAdd, sizeof(serveAdd)) == -1) {
    ERR_EXIT("connect");
  }
  while (1) {                   //死循环, 使用ctrl c信号关闭连接
    char buff[1024];
    printf("请说>>");
    scanf("%s", buff);
    write(sockfd, buff, strlen(buff));
    int n = read(sockfd, buff, sizeof(buff));
    if (n == -1) {
      ERR_EXIT("read");
    }
    buff[n] = 0;
    fprintf(stdout, ">>%s\n", buff);
  }
}

总结     (本文存在网络上的部分借鉴, 基本是出自本人自身理解, 存在不足之处希望大家帮助改正,共同学习进步)

通过上述:


我们主要需要掌握网络 ip地址 mac地址的  对比学习 以及含义,


mac 地址出场设定, 全球唯一.


ip 地址基于网络连接, 不同的网段下,或者同一网段下不同时候的连接  ip地址可能不一样. ip地址由 网络号 + 主机号  构成, 可以唯一标识一台主机.....


port端口号, 由于需要区别同一台  主机上面的  不同的 进程, port端口号主要是标识全网类唯一的一个进程


socket连接服务的本质: 是 全网中 为二两个 不同进程 之间 的 基于网络的相互通信.... 这种通信方式叫做 socket通信, 本质是打破同一主机通信的局限, 实现不同主机之间的通信


然后就是最基础网络通信的 TCP   模型了.....


通过上述模型, 存在一定的问题, 我们发现, 只有一个客户进行连接访问服务端的时候是没有问题的, 但是第二个客户端连接请求过来的时候, 竟然没有办法进行服务?????????


这样是不符合我们生活实际需求的, 也就是没办法并发服务多个客户端...这个问题提出来, 如何解决,大家可以在评论区进行讨论


相关文章
|
26天前
|
弹性计算 Linux Windows
如何升级计算巢服务实例版本,从而支持更便捷、可视化的游戏配置
现在你可以参考这篇教程,将之前老版本的幻兽帕鲁计算巢服务,升级到最新版本,支持最新版本的管理配置界面。包括修改游戏配置、重启游戏、存档管理等功能。
2436 2
如何升级计算巢服务实例版本,从而支持更便捷、可视化的游戏配置
|
1天前
|
弹性计算 数据可视化 Ubuntu
《Palworld/幻兽帕鲁》阿里云专用服务器搭建部署从入门到精通:2024年最新教程!
随着《幻兽帕鲁》的风靡,越来越多的玩家希望拥有属于自己的游戏服务器,以获得更加稳定和私密的游戏体验。今天,就为大家带来一份详细的新手教程,教您如何在阿里云上轻松搭建《幻兽帕鲁》专用服务器。 首先,您需要一台服务器。在这里,我们推荐使用阿里云服务器。它不仅稳定可靠,而且提供了丰富的幻兽帕鲁服务器搭建模板,让您轻松上手。
20 0
|
2天前
|
弹性计算 大数据 测试技术
2024年阿里云服务器价格购买价格表(实例配置价格+磁盘价格+网络宽带价格)
2024年阿里云服务器价格购买价格表来了!2024年阿里云服务器租用费用,轻量应用服务器和云服务器ECS优惠价格表,云服务器ECS经济型e实例2核2G、3M固定带宽99元一年、ECS u1实例2核4G、5M固定带宽、80G ESSD Entry盘优惠价格199元一年,轻量应用服务器2核2G3M带宽轻量服务器一年61元、2核4G4M带宽轻量服务器一年165元12个月、2核4G服务器30元3个月,幻兽帕鲁4核16G和8核32G服务器配置,云服务器ECS可以选择经济型e实例、通用算力u1实例、ECS计算型c7、通用型g7、c8i、g8i等企业级实例规格。今天分享阿里云服务器租用费用最新报价:
12 2
|
4天前
|
网络协议 网络安全 开发者
掌握Python网络编程:从TCP到UDP
掌握Python网络编程:从TCP到UDP
|
9天前
|
弹性计算 安全 数据中心
阿里云香港服务器提供高性能、安全、CN2高速网络、BGP多线精品
阿里云香港服务器提供高性能、安全、CN2高速网络、BGP多线精品
|
9天前
|
弹性计算 测试技术 数据中心
阿里云香港服务器BGP多线精品网络_CN2性能测试_中国香港主机测试
阿里云香港服务器BGP多线精品网络_CN2性能测试_中国香港主机测试,阿里云香港服务器中国香港数据中心网络线路类型BGP多线精品,中国电信CN2高速网络高质量、大规格BGP带宽,运营商精品公网直连中国内地,时延更低,优化海外回中国内地流量的公网线路,可以提高国际业务访问质量
|
20天前
|
网络协议 关系型数据库 MySQL
Linux下的网络编程——C/S模型TCP(二)
Linux下的网络编程——C/S模型TCP(二)
23 0
Linux下的网络编程——C/S模型TCP(二)
|
24天前
|
网络协议 网络安全
【网络安全 | 网络协议】结合Wireshark讲解TCP三次握手
【网络安全 | 网络协议】结合Wireshark讲解TCP三次握手
15 0
|
24天前
|
数据采集 网络协议 搜索推荐
网络编程【TCP单向通信、TCP双向通信、一对多应用、一对多聊天服务器】(二)-全面详解(学习总结---从入门到深化)
网络编程【TCP单向通信、TCP双向通信、一对多应用、一对多聊天服务器】(二)-全面详解(学习总结---从入门到深化)
33 0
|
25天前
|
网络协议 开发者 Python
tcp/ip模型中,帧是第几层的数据单元?
在网络通信的世界中,TCP/IP模型以其高效和可靠性而著称。这个模型是现代互联网通信的基石,它定义了数据在网络中如何被传输和接收。其中,一个核心的概念是数据单元的层级,特别是“帧”在这个模型中的位置。今天,我们就来说一下TCP/IP模型中帧的概念,以及它作为数据单元在哪一层中扮演着关键角色。