网络编程(TCP)

1. 网络字节序和主机字节序转化

  • 大端字节序:也称网络字节序,高位字节存储在内存的低地址处,低位字节存储在高地址处

  • 小端字节序:也称主机字节序,低位字节存储在内存的低地址处,高位字节存储在高地址处

1.1 相关函数

  • 将无符号短整数hostshort从主机字节顺序转换为网络字节顺序

    • uint16_t htons(uint16_t hostshort);

  • 将无符号短整数netshort从网络字节顺序转换为主机字节顺序

    • uint16_t ntohs(uint16_t netshort);

struct in_addr{
    uint32_t       s_addr; /* 网络字节顺序中的地址 */
}
// 此结构体包含上面结构体
struct sockaddr_in{
    sa_family_t    sin_family; /* 地址族:AF_INET */
    in_port_t      sin_port;   /* 端口号,网络字节顺序 */
    struct in_addr sin_addr;   /* 互联网地址 */
}
  • 将来自 IPv4 点分十进制表示法的 Internet 主机地址 cp 转换为二进制形式(以网络字节顺序)并将其存储在 inp 指向的结构体中。

    • int inet_aton(const char *cp, struct in_addr *inp);

    • 返回值:成功返回1,失败返回0

  • 字符串格式转换为sockaddr_in格式

    • int inet_pton(int af, const char *src, void *dst);

    • int af:通常为 AF_INET 用于IPv4地址,或 AF_INET6 用于IPv6地址

    • const char *src:包含IP地址字符串的字符数组

    • void *dst:指向一个足够大的缓冲区

    • 返回值:成功返回0,输入错误地址返回1,发生错误返回-1

2. TCP

2.1 TCP连接的建立和释放

三次握手

1.客户端发送SYN标志位1(表示这是一个同步报文)的报文,请求建立连接,这一过程为主动打开

2.服务端确认客户端的SYN,发送一个ACK和SYN标志均为1的报文

3.客户端确认服务端的报文,发送ACK标志为1的报文

四次挥手

1.客户端发送FIN标志为1的报文,请求断开连接,这一过程为主动关机

2.服务端确认客户端的FIN报文,客户端接收到第二次挥手信号,切换为FIN-WAIT-2状态 第二次挥手服务端仍可以发送数据

3.服务端发送FIN报文,这一过程称为被动关闭

4.客户端确认服务端发送的FIN报文,服务器接收到第四次挥手切换为CLOSEN状态 客户端发送第四次挥手信号后切换为TIME-WAIT状态,开始计时,等待2MSL(2倍最大报文时长,约定值)后进入CLOSEN状态

2.2 TCP所需用到的函数

  • 2.2.1 socket

    • 套接定义:允许不同主机上的进程通过网络进行数据交换,提供发送和接收数据的能力

  • 套接字组成

    • 网络地址:通常是IP地址,用于标识网络上的设备

    • 端口号:用于标识设备上的特定应用或进程,是一个16位数字

    • 协议:如TCP或UDP,定义数据传输的规则和格式

  • 套接字类型:流套接字(TCP)和数据报套接字(UDP)

  • 创建套接字

    • int socket(int domain,int type,int protocol);

    • int domain:指定要创建套接字的通信域 一般写AF_INFT(IPv4互联网协议)

    • int type:指定要创建的socket类型,SOCK_STREAM(TCP)、SOCK_DGRAM(UDP)

    • int protocol:补充协议 推荐写0

    • 返回值:成功返回文件描述符 失败-1

  • 2.2.2 绑定地址和端口号

    • int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen);

    • int sockfd:套接字文件描述符

    • const struct sockaddr *addr:结构体,指定的地址、地址长度和格式取决于socket地址族

    • socklen_t addrlen:指定addr指向的结构体长度

    • 返回值:成功0 失败-1

  • 2.2.3 服务端进入监听状态

    • int listen(int sockfd,int backlog);

    • int sockfd:套接字文件描述符

    • int backlog:指定还未accpet但是已经完成链接的队列长度

    • 返回值:成功0 失败-1

  • 2.2.4 服务端提取第一个连接请求,创建新套接字

    • int accept(int sockfd,struct sockaddr * addr,socklen_t *addrlen)

    • int sockfd:套接字文件描述符

    • const struct sockaddr *addr:结构体,指定的地址、地址长度和格式取决于socket地址族

    • socklen_t addrlen:指定addr指向的结构体长度

    • 返回值:成功0 失败-1

  • 2.2.5 客户端调用,与服务器建立连接

    • int connect(int sockfd,const struct sockaddr *addr,socklen_t addrlen);

    • int sockfd:客户端套接字文件描述符

    • const struct sockaddr *addr:指向sockaddr结构体指针,包含目的地址信息

    • socklen_t addrlen:指定addr指向的结构体长度

    • 返回值:成功0 失败-1

  • 2.2.6 传输信息

    • ssize_t send(int sockfd,const void *buf,size_t len,int flags);

    • int sockfd:发送套接字文件描述符

    • const void *buf:发送缓冲区 const修饰为"只读"

    • size_t len:要发送的数据的字节长度

    • int flags:通常设置为0

    • 返回值:成功返回发送的字节数,失败-1

  • 2.2.7 接收信息

    • ssize_t recv(int sockfd,void *buf,size_t len,int flags);

    • int sockfd:套接字文件描述符

    • void *buf:接收缓冲区

    • size_t len:缓冲区长度

    • int flags:通常设置为0

    • 返回值:成功返回接收到的字节数,失败-1

  • 2.2.8 关闭一部分套接字或全部连接

    • int shutdown(int sockfd,int how);

    • int sockfd:套接字文件描述符

    • int how:指定关闭的类型

    • 返回值:成功0 失败-1

  • 2.2.9 直接全部关闭

    • int close(int __fd);

    • int __fd:要关闭的文件描述符

    • 返回值:成功0 失败-1

3. 示例代码(单个服务器和单个客户端)

3.1 服务端

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>

#define handle_error(cmd,result) \
    if(result < 0)                \
    {                             \
        perror(cmd);              \
        return -1;                \
    }  

void *read_from_client(void *arg)
{
    int client_fd = *(int *)arg;
    char *read_buff = malloc(sizeof(char) * 1024);
    ssize_t count = 0;
    if(!read_buff)
    {   
        perror("read_buff");
        return NULL;
    }
    // 只要接收到消息就一直挂着
    while(count = recv(client_fd,read_buff,1024,0))
    {
        if(count < 0){
            perror("recv");    
        }
        // 将接收到的数据打印到控制台
        fputs(read_buff,stdout);

    }
    printf("客户端请求连接\n");
    free(read_buff); // 释放内存
    return NULL;
}


void *write_to_client(void *arg)
{
    int client_fd = *(int *)arg;
    char *write_buff = malloc(sizeof(char) * 1024);
    ssize_t count = 0;
    if(!write_buff)
    {
        perror("write_buff");
        return NULL;
    }
    // 获取控制台输入的数据
    while(fgets(write_buff,1024,stdin)){
        //发送数据
        count = send(client_fd,write_buff,1024,0);
        if(count < 0)
        {
            perror("send");
        }
    }
    printf("接收到控制台的关闭请求 不再写入 关闭连接\n");
    // 可以具体到某一端
    shutdown(client_fd,SHUT_WR);
    free(write_buff);
    return NULL;
    
}

int main(int argc, char const *argv[])
{

    struct sockaddr_in server_addr,client_addr;
    int sockfd,temp_result,client_fd;
    pthread_t read,write;
    // 清空
    memset(&server_addr,0,sizeof(server_addr));
    memset(&client_addr,0,sizeof(client_addr));
    // 设置服务端的IP和端口 使用ipv4
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(6666);   
    
    // TCP服务端编程步骤
    // 1.创建socket
    sockfd = socket(AF_INET,SOCK_STREAM,0);
    handle_error("socket",sockfd);

    // 2.绑定端口和ip
    temp_result = bind(sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr));
    handle_error("bind",temp_result);

    // 3.进入监听状态
    temp_result = listen(sockfd,1024);
    handle_error("listen",temp_result);

    // 4.与客户端获得连接
    socklen_t client_len = sizeof(client_addr);
    client_fd = accept(sockfd,(struct sockaddr *)&client_addr,&client_len);
    handle_error("accept",client_fd);

    printf("与客户端%s %d建立连接 文件描述符%d\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port),client_fd);
    // 创建两个线程 一个读 一个写
    pthread_create(&read,NULL,read_from_client,(void *)&client_fd);
    pthread_create(&write,NULL,write_to_client,(void *)&client_fd);

    // 阻塞主线程 等待子线程运行完成
    pthread_join(read,NULL);
    pthread_join(write,NULL);

    // 释放资源
    close(sockfd);
    close(client_fd);
    return 0;
}

3.2 客户端

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>

#define handle_error(cmd,result) \
    if(result < 0)                \
    {                             \
        perror(cmd);              \
        return -1;                \
    }  

void *read_from_server(void *arg)
{
    int client_fd = *(int *)arg;
    char *read_buff = malloc(sizeof(char) * 1024);
    ssize_t count = 0;
    if(!read_buff)
    {   
        perror("read_buff");
        return NULL;
    }
    // 只要接收到消息就一直挂着
    while(count = recv(client_fd,read_buff,1024,0))
    {
        if(count < 0){
            perror("recv");
        }
        // 将接收到的数据打印到控制台
        fputs(read_buff,stdout);

    }
    printf("服务端请求关闭\n");
    free(read_buff); // 释放内存
    return NULL;
}


void *write_to_server(void *arg)
{
    int client_fd = *(int *)arg;
    char *write_buff = malloc(sizeof(char) * 1024);
    ssize_t count = 0;
    if(!write_buff)
    {
        perror("write_buff");
        return NULL;
    }
    // 获取控制台输入的数据
    while(fgets(write_buff,1024,stdin)){
        //发送数据
        count = send(client_fd,write_buff,1024,0);
        if(count < 0)
        {
            perror("send");
        }
    }
    printf("接收到控制台的关闭请求 不再写入 关闭连接\n");
    // 可以具体到某一端
    shutdown(client_fd,SHUT_WR);
    free(write_buff);
    return NULL;
    
}

int main(int argc, char const *argv[])
{

    struct sockaddr_in server_addr,client_addr;
    int sockfd,temp_result,client_fd;
    pthread_t read,write;
    // 清空
    memset(&server_addr,0,sizeof(server_addr));
    memset(&client_addr,0,sizeof(client_addr));
    // 设置客户端的IP和端口 使用ipv4
    client_addr.sin_family = AF_INET;
    inet_pton(AF_INET,"自己的IP(虚拟机)",&client_addr.sin_addr);
    client_addr.sin_port = htons(8888);    

    // 设置服务端的IP和端口 使用ipv4
    server_addr.sin_family = AF_INET;
    inet_pton(AF_INET,"127.0.0.1",&server_addr.sin_addr);
    server_addr.sin_port = htons(6666);  //注意这里的端口号要和服务端一致
    
    // TCP客户端编程步骤
    // 1.创建socket
    sockfd = socket(AF_INET,SOCK_STREAM,0);
    handle_error("socket",sockfd);

    // 2.绑定端口和ip
    temp_result = bind(sockfd,(struct sockaddr *)&client_addr,sizeof(client_addr));
    handle_error("bind",temp_result);

    // 3.主动连接服务器
    temp_result = connect(sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr));
    handle_error("connect",temp_result);

    printf("连接上服务器%s %d\n",inet_ntoa(server_addr.sin_addr),ntohs(server_addr.sin_port));
    // 创建两个线程 一个读 一个写
    pthread_create(&read,NULL,read_from_server,(void *)&sockfd);
    pthread_create(&write,NULL,write_to_server,(void *)&sockfd);

    // 阻塞主线程 等待子线程运行完成
    pthread_join(read,NULL);
    pthread_join(write,NULL);

    // 释放资源
    close(sockfd);
    close(client_fd);
    return 0;
}

3.3 运行结果

客户端和服务端可以互相收发消息

  • 23
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值