网络编程之TCP编程

基于 C/S :客户端(client)/服务器端(server)

1.流程

2. 函数接口

所有函数所需头文件:

cpp 复制代码
#include <sys/types.h> 
#include <sys/socket.h>

系统定义好了用来存储网络信息的结构体

ipv4通信使用的结构体:struct sockaddr_in

我们只需要直接定义结构体变量即可

2.1 创建套接字socket()

cpp 复制代码
int socket(int domain, int type, int protocol);
功能:创建套接字
参数:

   domain:协议族
     AF_UNIX, AF_LOCAL  本地通信
     AF_INET            ipv4
     AF_INET6            ipv6

  type:套接字类型
     SOCK_STREAM:流式套接字
     SOCK_DGRAM:数据报套接字
      SOCK_RAW:原始套接字

  protocol:协议  一般填0 自动匹配底层 
     根据type系统默认自动帮助匹配对应协议
     传输层:IPPROTO_TCP、IPPROTO_UDP、IPPROTO_ICMP
     网络层:htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL)
  
 返回值:
    成功 文件描述符-- > sockfd (用于连接)
    失败 -1,更新errno

注意:TCP服务器端有两类文件描述符 !!!
一类用于连接的文件描述符(sockfd-->socket函数返回值) 只有一个
一类用于通信的文件描述符(acceptfd-->accept函数返回值) 可以多个

2.2绑定套接字bind()

cpp 复制代码
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:绑定
参数:
    socket:套接字
    addr:用于通信结构体 (提供的是通用结构体,需要根据选择通信方式,填充对应结构体-通信当时socket第一个参数确定)   
    addrlen:结构体大小   
返回值:成功 0   失败-1,更新errno

由于系统定义好的记录网络信息的结构体是struct sockaddr_in类型,因此,bind第二个参数使用时结构体变量地址的时候要强制类型转换

2.3监听listen()

cpp 复制代码
int listen(int sockfd, int backlog);
功能:监听,将主动套接字变为被动套接字
参数:
 sockfd:套接字
 backlog:(目前已无具体作用,写个正数即可)
    同时响应客户端请求链接的最大个数,不能写0.
    不同平台可同时链接的数不同,一般写6-8个
    (队列1:保存正在连接)
    (队列2,连接上的客户端)

返回值:成功 0   失败-1,更新errno 

注意:listen作用:主动套接字变为被动套接字!!!

2.4接收客户端连接请求 accept()

cpp 复制代码
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
accept(sockfd,NULL,NULL);
功能:阻塞函数,阻塞等待客户端的连接请求,如果有客户端连接,
则accept()函数返回,返回一个用于通信的套接字文件描述符;
参数:
   Sockfd :套接字
   addr: 链接客户端的ip和端口号
      如果不需要关心具体是哪一个客户端,那么可以填NULL;
   addrlen:结构体的大小
     如果不需要关心具体是哪一个客户端,那么可以填NULL;
返回值: 
     成功:文件描述符; //用于通信
		失败:-1,更新errno

2.5接受消息recv()

cpp 复制代码
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   成功接收的字节个数

2.6发送消息send()

cpp 复制代码
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:发送数据
参数:
    sockfd:socket函数的返回值
    buf:发送内容存放的地址
    len:发送内存的长度
    flags:如果填0,相当于write();

2.7连接服务器connect()

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

2.8 关闭套接字 close()

即关闭套接字文件

close(文件描述符);

3.服务器端

按照流程:

(1)创建流式套接字socket()

(2)指定网络信息

(3)绑定套接字bind()

(4) 监听listen()

(5) 等待客户连接信息accept()

注意:

在服务器端使用客户的网络信息时:

(6)收发消息 send() recv()

(7) 关闭套接字

源代码:

cpp 复制代码
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>

//  $$ 服务器端 $$  

int main(int argc, char const *argv[])
{
    /*创建流式套接字*/
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket err");
        return -1;
    }
    else
    {
        printf("创建套接字成功\n");
    }

    /*指定服务器网络信息 使用的协议族(IPv4-->AF_INET)、IP地址、端口号等*/

    // 服务器的网络信息通过一个系统定义好的结构体来描述
    struct sockaddr_in saddr;                     // 定义一个结构体变量
    saddr.sin_family = AF_INET;                   // 确定协议族-->IPv4
    saddr.sin_port = htons(atoi(argv[1]));        // 确定使用的端口号
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0"); // 确定服务器IP地址

    /*绑定套接字*/

    int t1 = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));
    if (t1 < 0)
    {
        perror("bind err");
        return -1;
    }
    else
    {
        printf("绑定套接字成功\n");
    }

    /*监听*/
    int t2 = listen(sockfd, 6); // 将默认的主动套接字变为被动套接字
    if (t2 < 0)
    {
        printf("listen err");
        return -1;
    }
    else
    {
        printf("监听中\n");
    }

    
    int acceptfd;
    char buf[128] = "";
    int ret;

    // 定义一个结构体变量来存接收到的客户信息
    struct sockaddr_in caddr;
    int len = sizeof(caddr); // len是记录客户信息的结构体的大小
    while (1)
    {

        /*阻塞等待接收客户端的连接请求,并将连接成功的客户端信息写入到结构体变量caddr中*/
        acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);
        if (acceptfd < 0)
        {
            printf("accept err");
            return -1;
        }
        else
        {
            printf("等待接收客户端请求\n");
        }

        printf("客户IP:%s 端口号:%d \n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));

        while (1)
        {
            /*接收消息*/
            ret = recv(acceptfd, buf, 128, 0); // 0-->相当于read(acceptfd,buf,128)
            if (ret < 0)
            {
                perror("recv err");
                return -1;
            }
            else if (ret == 0)
            {
                printf("客户退出\n");
                break;
            }
            else
            {
                printf("%s 接收成功\n", buf);
                memset(buf, 0, sizeof(buf));
            }
        }
        close(acceptfd);
    }
    /* 关闭套接字 */

    close(sockfd);

    return 0;
}

4.客户端

按照流程:

(1)创建流式套接字socket()

(2)指定服务器网络信息

(3)连接服务器connect()

(4)发送接受消息 send() recv()

(5)关闭套接字

源代码:

cpp 复制代码
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
//  $$ 客户端 $$  

int main(int argc, char const *argv[])
{
    /*创建流式套接字*/
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket err");
        return -1;
    }
    else
    {
        printf("创建套接字成功\n");
    }

    /*指定服务器的网络信息*/
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[1]));
    saddr.sin_addr.s_addr = inet_addr(argv[2]);

    /* 请求连接服务器*/
    int t = connect(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));
    if (t < 0)
    {
        perror("connect err");
        return -1;
    }
    else
    {
        printf("connect success\n");
    }

    char buf[128] = "";

    /* 发送消息 */
    while (1)
    {

        fgets(buf, sizeof(buf), stdin);
        if (buf[strlen(buf) - 1] == '\n')
        {
            buf[strlen(buf) - 1] = '\0';
        }

        if (strcmp(buf, "quit") == 0)
        {
            break;
        }

        send(sockfd, buf, sizeof(buf), 0); // 0-->相当于write(sockfd,buf,sizeof(buf))
    }

    /* 关闭套接字 */
    close(sockfd);

    return 0;
}

5.TCP粘包问题

tcp粘包

tcp拆包

6.三次握手四次挥手

三次握手建立连接
第一次握手:客户通过调用connect进行主动打开(active open)。这引起客户TCP发送一个SYN(表示同步)分节(SYN=J),它告诉服务器客户将在连接中发送数据的初始序列号。并进入SYN_SEND状态,等待服务器的确认。

第二次握手:服务器必须确认客户的SYN,同时自己也得发送一个SYN分节,它含有服务器将在同一连接中发送的数据的初始序列号。服务器以单个字节向客户发送SYN和对客户SYN的ACK(表示确认),此时服务器进入SYN_RECV状态。

第三次握手:客户收到服务器的SYN+ACK。向服务器发送确认分节,此分节发送完毕,客户服务器进入ESTABLISHED状态,完成三次握手。

相关推荐
网硕互联的小客服1 小时前
如何利用Elastic Stack(ELK)进行安全日志分析
linux·服务器·网络·安全
浩浩测试一下2 小时前
Authpf(OpenBSD)认证防火墙到ssh连接到SSH端口转发技术栈 与渗透网络安全的关联 (RED Team Technique )
网络·网络协议·tcp/ip·安全·网络安全·php
leagsoft_10033 小时前
联软NSPM自动化策略管理 助力上交所加速国产化替代提升运维效率
运维·网络·自动化
孤寂大仙v3 小时前
【计算机网络】网络层IP协议与子网划分详解:从主机通信到网络设计的底层逻辑
tcp/ip·计算机网络·php
漫步者TZ4 小时前
【Netty系列】解决TCP粘包和拆包:LengthFieldBasedFrameDecoder
java·网络协议·tcp/ip·netty
leagsoft_10034 小时前
筑牢企业网管域安全防线,守护数字核心——联软网管域安全建设解决方案
网络·安全·网络安全
苦学编程的谢5 小时前
Java网络编程API 1
java·开发语言·网络
alien爱吃蛋挞5 小时前
【JavaEE】万字详解HTTP协议
网络·网络协议·http
hgdlip7 小时前
重启路由器ip不变怎么回事?原因分析与解决方法
网络协议·tcp/ip·智能路由器
vortex58 小时前
浅谈 Linux 防火墙:从原理到实践
linux·网络·php