TCP编程及基础知识

一、端口号

  • 为了区分一台主机接收到的数据包应该转交给哪个进程来进行处理,使用端口号来区分
  • TCP端口号与UDP端口号独立
  • 端口用两个字节来表示    2byte(65535个)

众所周知端口:1~10231~255之间为众所周知端口,256~1023端口通常由UNIX系统占用)
已登记端口:1024~49151    (选1000以上10000以下) 
动态或私有端口:49152~65535

二、字节序  

   小端序(little-endian)  - 低序字节存储在低地址

   大端序(big-endian)    - 高序字节存储在低地址

网络中传输一字节以上的带类型的数据(比如short、int),必须使用网络字节序,即大端字节序。

TCP编程及基础知识_第1张图片

查看主机是大端序还是小端序。

TCP编程及基础知识_第2张图片

网络传输中,需要将每个主机的主机字节序(CPU决定),转换为网络中统一顺序的网络字节序

才能供双方主机去识别

只需要转换IP和port就可以,不需要转换传输的数据包的字节序

因为IP和port为 4个字节和2个字节,  而数据报一般都为char类型, 占一个字节

根据字节序的性质,内存存储大于一个字节类型的数据在内存中的存放顺序

所以char类型并不具有字节序的概念

1.主机字节序到网络字节序 (小端序->大端序)

#include 
u_long htonl (u_long hostlong); //host to internet long
功能:将无符号整数hostlong从主机字节顺序转换为网络字节顺序。
#include 
u_short htons (u_short short);  //掌握这个
功能:将无符号短整数hostshort从主机字节顺序到网络字节顺序。

2.网络字节序到主机字节序(大端序->小端序)

#include 
u_long ntohl (u_long hostlong);
功能:将无符号整数netlong从网络字节顺序转换为主机字节顺序。
#include 
u_short ntohs (u_short short);//端口 2byte
功能:将无符号短整数netshort从网络字节顺序转换为主机字节顺序。的

#include 
#include 

int main(int argc, char const *argv[])
{
    int a = 0x12345678;
    //小端转大端
    int b = htonl(a);
    printf("%#x\n", b);
    //大端转小端
    int c = ntohl(b);
    printf("%#x\n", c);
    return 0;
}

TCP编程及基础知识_第3张图片

三、IP地址转换

1、inet_addr主机字节序转换为网络字节序

#include   
#include               
#include
in_addr_t  inet_addr(const char *strptr);  //该参数是字符串
 
typedef uint32_t in_addr_t;
struct in_addr 
{
    in_addr_t s_addr;
};
功能:  主机字节序转为网络字节序
参数:  const char *strptr: 字符串
返回值: 返回一个无符号长整型数(无符号32位整数用十六进制表示), 
      否则NULL

2、inet_ntoa 网络字节序转换为主机字节序

#include 
#include 
#include 
char *inet_ntoa(struct in_addr inaddr);
功能:   将网络字节序二进制地址转换成主机字节序。 
参数:  struct in_addr in addr  : 只需传入一个结构体变量
返回值:  返回一个字符指针, 否则NULL;

TCP编程及基础知识_第4张图片

TCP编程及基础知识_第5张图片

四、TCP编程

C/S   B/S

client/server              browser/server

客户端/服务器 浏览器/服务器

1、套接字工作流程

TCP编程及基础知识_第6张图片

TCP编程及基础知识_第7张图片

客户端:   发送请求

服务器端:  相应请求

服务器:

1.创建流式套接字(socket())------------------------>  有手机

2.指定本地的网络信息(struct sockaddr_in)----------> 有号码

3.绑定套接字(bind())------------------------------>绑定手机

4.监听套接字(listen())---------------------------->待机

5.链接客户端的请求(accept())---------------------->接电话

6.接收/发送数据(recv()/send())-------------------->通话

7.关闭套接字(close())----------------------------->挂机

客户端:

1.创建流式套接字(socket())----------------------->有手机

2.指定服务器的网络信息(struct sockaddr_in)------->有对方号码

3.请求链接服务器(connect())---------------------->打电话

4.发送/接收数据(send()/recv())------------------->通话

5.关闭套接字(close())--------------------------- >挂机

服务器端(server):

1) socket(),创建套接字文件,创建出用于连接的套接字文件

2) bind(), 绑定,把socket()函数返回的文件描述符和IP、端口号进行绑定;

3) listen(), 监听,将socket()返回的文件描述符,由主动套接字变为被动套接字;

4) accept(), 阻塞函数,阻塞等待客户端的连接请求, 返回一个用于通信的套接字文件;

5) recv(), 接收客户端发来的数据;(read)    

//6) send(), 发送数据;(write)

  1. close(), 关闭文件描述符; 至少要关闭: 连接、通信

客户端(client):

  1. socket(),创建套接字文件,既用于连接,也用于通信; 

    填充结构体:  填充服务器的ip和端口 , 用于connect连接

2) connect(); 用于发起连接请求,阻塞等待连接服务器;

3) send(), 发送数据;

//4) recv(), 接收数据;

5)close(), 关闭文件描述符;

TCP编程及基础知识_第8张图片

2、函数接口

1)socket 创建套接字

#include           /* See NOTES */
#include 
int socket(int domain, int type, int protocol);
功能:创建套接字
参数:
   domain:协议族
     AF_UNIX, AF_LOCAL  本地通信
     AF_INET            ipv4
     AF_INET6            ipv6
   type:套接字类型
     SOCK_STREAM:流式套接字
     SOCK_DGRAM:数据报套接字
   protocol:协议 - 填0 自动匹配底层 ,根据type系统默认自动帮助匹配对应协议
       传输层:IPPROTO_TCP、IPPROTO_UDP、IPPROTO_ICMP
       网络层:htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL)
 返回值:
    成功 文件描述符 0 -> 标准输入  1->标准输出  2->标准出错 
                  3->socket
    失败 -1,更新errno

TCP编程及基础知识_第9张图片

2)bind 绑定   ipv4  ip和端口 

#include           /* See NOTES */
#include 
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:绑定   ipv4  ip和端口 
参数
   sockfd:文件描述符
   addr:通用结构体,根据socket第一个参数选择的通信方式最终确定这需要真正填充传递的结构体是那个类型。强转后传参数。
   addrlen:填充的结构体的大小   
返回值:成功0 失败-1、更新errno

通用结构体:相当于预留一个空间
struct sockaddr 
{
    sa_family_t sa_family;
    char        sa_data[14];
}

ipv4的结构体 
 struct sockaddr_in 
 {
     sa_family_t    sin_family;  //协议族AF_INET
     in_port_t      sin_port;  //端口号
     struct in_addr sin_addr;  //ip地址
 };
  struct in_addr 11
 {
     uint32_t       s_addr;   //IP地址  
 };
 
 本地址通信结构体:
  struct sockaddr_un 
  {
     sa_family_t sun_family;  //AF_UNIX  
     char        sun_path[108]; //在本地创建的套接字文件的路径及名字
 };
 
ipv6通信结构体:
struct sockaddr_in6 
{
    sa_family_t     sin6_family;   
    in_port_t       sin6_port;     
    uint32_t        sin6_flowinfo; 
    struct in6_addr sin6_addr;     
    uint32_t        sin6_scope_id; 
};
struct in6_addr 
{
    unsigned char   s6_addr[16];   
};
//如果绑定使用通用地址可使用 INADDR_ANY宏(结合代码讲的时候再添加)
含义是自动绑定所有本机网卡的地址,

TCP编程及基础知识_第10张图片

TCP编程及基础知识_第11张图片

11.2作业

3)listen 监听,将主动套接字变为被动套接字

#include           /* See NOTES */
#include 
int listen(int sockfd, int backlog);
功能:监听,将主动套接字变为被动套接字
参数:
 sockfd:套接字
 backlog:同时响应客户端请求链接的最大个数,不能写0.
不同平台可同时链接的数不同,一般写6-8
返回值:成功 0   失败-1,更新errno  

TCP编程及基础知识_第12张图片

4)accept 阻塞函数

#include 
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

accept(sockfd,NULL,NULL);
阻塞函数,阻塞等待客户端的连接请求,如果有客户端连接,
accept()函数返回,返回一个用于通信的套接字文件描述符(4);
参数:
   Sockfd :套接字
   addr: 链接客户端的ip和端口号
      如果不需要关心具体是哪一个客户端,那么可以填NULL;
   addrlen:结构体的大小
     如果不需要关心具体是哪一个客户端,那么可以填NULL;
     需要查看链接的客户端ip和端口号
     accept(sockfd,NULL,NULL);
     需要查看链接的客户端ip和端口号
     accept(sockfd,(struct sockaddr *)&saddr,&len);
  返回值: 
     成功:文件描述符; //用于通信
失败:-1,更新errno
同时返回客户端的ip和端口号。保存到结构体struct sockaddr *addr中。
通过结构体访问到:inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port)
打印时记得使用 inet_ntoa()和ntohs()转换为主机字节序

TCP编程及基础知识_第13张图片

5)recv 接收数据 

#include 
#include 
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能: 接收数据 
参数: 
    sockfd: acceptfd ;
    buf  存放位置
    len  大小
    flags  一般填0,相当于read()函数
    MSG_DONTWAIT  非阻塞
返回值: 
   < 0  失败出错  更新errno
   ==0  表示客户端退出
   >0   成功接收的字节个数

发送端协议类型为TCP Client,远程目标ip地址为服务器IP地址

端口号也应该一致

先关闭客户端,再关闭服务器。

TCP编程及基础知识_第14张图片

6)connect 用于连接服务器;

#include           /* See NOTES */
#include 
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:用于连接服务器;
参数:
     sockfd:socket函数的返回值
     addr:填充的结构体是服务器端的;
     addrlen:结构体的大小
返回值 
      -1 失败,更新errno
      正确 0 

TCP编程及基础知识_第15张图片

7)send 发送数据,用来发送消息到一个套接字中。

#include 
#include 
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:发送数据,用来发送消息到一个套接字中。只能在套接字处于连接状态的时候才能使用。
参数:
    sockfd:socket函数的返回值
    buf:发送内容存放的地址
    len:发送内存的长度
    flags:如果填0,相当于write();
返回值;
    成功 发送的字节数
    失败 -1
可以使用write代替send,  都属于往发送缓存区内写入数据
read代替recv , 都属于往接收缓存区内提取数据

TCP编程及基础知识_第16张图片

测试注意:

1. 如果使用客户端软件进行连接,必须保证windows和虚拟机在同一个局域网(桥接),并能互相ping通。服务器的IP地址必须指定为虚拟机自己的IP。

2. 必须保证客户端正常退出后在关闭服务器程序,在客户端连接状态情况下强制关闭服务器程序,下次启动服务器程序后会提示bind err。这是因为没有正常释放绑定的端口,等1~2分钟就可以了。

TCP编程及基础知识_第17张图片

3、代码优化

1。端口和ip地址通过命令行传参到代码中。(如果参数不一致,应该提示并退出)
2。服务器端IP地址可由“0.0.0.0”自动获取,或者由宏定义自动获取

TCP编程及基础知识_第18张图片

3。客户端发送去掉fgets获取的多余的'\n'.

fgets(实际读到的内容小于等于指定个数-1,自动读到的内容后添加’\0’,会将’\n’也读入)

  if(buf[strlen(buf)-1] == '\n')//去掉fgets获取的'\n'

         buf[strlen(buf)-1] ='\0';

TCP编程及基础知识_第19张图片

TCP编程及基础知识_第20张图片

4.设置来电显示功能,获取到请求链接服务器的客户端的ip和端口。

 int acceptfd = accept(sockfd,(struct sockaddr *)&caddr,&len);

打印时记得使用 inet_ntoa()和ntohs()转换为主机字节序

printf("client ip:%s ,port:%d\n", inet_ntoa(saddr.sin_addr),ntohs(saddr.sin_port));

TCP编程及基础知识_第21张图片

5、当客户端输入quit的时候,客户端退出

   strncmp

TCP编程及基础知识_第22张图片

6.实现循环服务器,服务器不退出,当链接服务器的客户端退出,服务器等到下一个客户端链接。

  accept处使用while到接受数据结束。

/*服务器创建代码 */
#include 
#include  /* See NOTES */
#include 
#include 
#include  /* superset of previous */
#include 
#include 
#include 
#include 
int main(int argc, char const *argv[])
{
    if (argc < 2)
    {
        printf("plase input \n");
        return -1;
    }
    //1.创建套接字,用于链接
    int sockfd;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket err");
        return -1;
    }
    //文件描述符 0 -> 标准输入  1->标准输出  2->标准出错  3->socket
    printf("sockfd:%d\n", sockfd);

    //2.绑定 ip+port 填充结构体
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;            //协议族ipv4
    saddr.sin_port = htons(atoi(argv[1])); //端口号,htons将无符号短整数hostshort从主机字节顺序到网络字节顺序。
//atoi(),字符串转整型
//saddr.sin_addr.s_addr = inet_addr(argv[1]);//ip地址,转化为16进制表示
#if 0
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");//ip地址,转化为16进制表示
#else
    saddr.sin_addr.s_addr = INADDR_ANY;
#endif
    socklen_t len = sizeof(saddr); //结构体大小
    //bind绑定ip和端口
    if (bind(sockfd, (struct sockaddr *)&saddr, len) < 0)
    {
        perror("bind err");
        return -1;
    }
    printf("bind success\n");
    //3.启动监听,把主动套接子变为被动套接字
    if (listen(sockfd, 6) < 0)
    {
        perror("listen err");
        return -1;
    }
    printf("listen success\n");
    //4.阻塞等待客户端的链接请求
    int acceptfd;
    // acceptfd = accept(sockfd,NULL,NULL);
    while (1)
    {
        acceptfd = accept(sockfd, (struct sockaddr *)&saddr, &len);
        //获取客户端的ip和端口,(struct sockaddr *)&saddr:用来存放返回的ip,和端口
        if (acceptfd < 0)
        {
            perror("accept err");
            return -1;
        }
        printf("client ip:%s ,port:%d\n", inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port));
        printf("connect success\n");
    //5.接收数据
    char buf[64];
    int ret;
    while (1)
    {
        ret = recv(acceptfd, buf, sizeof(buf), 0);
        if (strncmp(buf, "quit", 4) == 0) //接收到quit退出
        {
            break;
        }
        if (ret < 0)
        {
            perror("recv err.");
            return -1;
        }
        else if (ret == 0) //客户端退出
        {
            printf("client exit\n");
            break;
        }
        else
        {
            printf("buf:%s\n", buf);
        }
    }
    }
    close(sockfd);
    close(acceptfd);
    return 0;
}

/*客户端创建代码 */
#include 
#include  /* See NOTES */
#include 
#include 
#include  /* superset of previous */
#include 
#include 
#include 
#include 
int main(int argc, char const *argv[])
{
    if (argc < 3)
    {
        printf("plase input \n");
        return -1;
    }
    //1.创建套接字,用于链接
    int sockfd;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket err");
        return -1;
    }
    //文件描述符 0 -> 标准输入  1->标准输出  2->标准出错  3->socket
    printf("sockfd:%d\n", sockfd);
    //2.绑定 ip+port 填充结构体
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;                 //协议族ipv4
    saddr.sin_port = htons(atoi(argv[2]));      //端口号,htons将无符号短整数hostshort从主机字节顺序到网络字节顺序。
    saddr.sin_addr.s_addr = inet_addr(argv[1]); //ip地址,转化为16进制表示
    socklen_t len = sizeof(saddr);              //结构体大小
    //3用于连接服务器;
    if (connect(sockfd, (struct sockaddr *)&saddr, len) < 0)
    {
        perror("connect err");
        return -1;
    }
    //4发送信息
    char buf[64] = {0};
    while (1)
    {
        fgets(buf, sizeof(buf), stdin);   //从终端获取内容存放到数组中
        if (strncmp(buf, "quit", 4) == 0) //输入quit退出客户端
        {
            break;
        }
        if (buf[strlen(buf)] == '\0')
        {
            buf[strlen(buf) - 1] = '\0';
        }                                  //将fgets自动补的\n去掉
        send(sockfd, buf, sizeof(buf), 0); //将数组中的内容发送到主机端
    }
    close(sockfd);
    // close()
    return 0;
}

ftp文件传输协议

tcp实现ftp功能:

模拟FTP核心原理:客户端连接服务器后,向服务器发送一个文件。文件名可以通过参数指定,服务器端接收客户端传来的文件(文件名随意),如果文件不存在自动创建文件,如果文件存在,那么清空文件然后写入。

项目功能介绍:

均有服务器和客户端代码,基于TCP写的。

在同一路径下,将客户端可执行代码复制到其他的路径下,接下来再不同的路径下运行服务器和客户端。

相当于另外一台电脑在访问服务器。

客户端和服务器链接成功后出现以下提示:四个功能

***************list**************//列出服务器所在目录下的文件名(除目录不显示)

***********put filename**********//客户端给服务器上传一个文件

***********get filename**********//客户端从服务器所在路径下载文件

**************quit***************//退出(可只退出客户端,服务器等待下一个客户端链接)

//读文件,读到最后一次,最后再发送end

//粘包:1.延时 2.读多少发多少

服务器

  1. 搭建tcp框架
  2. 服务器接收消息

//list 列出服务器所在目录下的文件名 目录操作 文件属性获取--发送给客户端

//put filename //接收文件 recv --》新建文件---》 写文件 

//get filename //读文件--》发送给客户端

//quit  while(1)accpet;

客户端

  1. 搭建tcp框架
  2. 列出功能
  3. 给服务器发消息

//list 接收--》列出服务器所在目录下的文件名

//put filename 找到文件--》读文件   -》发送

//get filename //接收文件 recv --》新建文件---》 写文件 

//quit   

你可能感兴趣的:(网络编程,C语言,开发语言,linux,IO,网络协议,udp,tcp/ip,c语言)