linux-socket tcp客户端服务器编程模型及代码详解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/AXuan_K/article/details/52294495

上一篇文章介绍了 TCP/IP相关协议,socket通信流程和涉及到的各种函数:

Socket简单理解


本篇将具体解释tcp客户端服务器编程模型相关的代码

文章分为4个部分:

1. TCP客户端服务器编程模型流程图

2. 网络字节序与主机字节序

3. TCP编程的地址结构

4. 详细案例代码及解释

一: TCP客户端服务器编程模型流程图


这里写图片描述

上面两张图片将整个流程已经说明的很清楚了;

二: 网络字节序与主机字节序

字节序即是保存数据的方向方式, 分为 大端存储小端存储;

其中 网络字节序 使用的是大端存储, 而我们用的主机字节序默认采用的小端存储

所以在我们进行网络编程的过程中还需要对相应的数据(地址 端口)进行字节序转换

下面是几个字节序的转换函数:

这里写图片描述
这里写图片描述

每个函数都它特定的意思 比如第一张图中的 第一个函数htonl 还有第二张图中的ntop

字符 含义
h host(主机)
to to
n network
l long
p pointer

这样就很好记忆了

三: TCP编程的地址结构

第一个是通用的地址结构

这里写图片描述

第二个则是封装过的

这里写图片描述

这两个数据类型可以相互转换

四: 详细案例代码及解释

下面给出一个案例的代码.完成如下功能:

服务器接收来自客户端的连接, 服务器在屏幕输出客户端的地址;

并向客户端发送当前的时间, 客户端再向屏幕输出时间.

服务端代码:

//tcp_server.c
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <time.h>
#include <memory.h>
#include <signal.h>

void print(struct sockaddr_in *addr){
    int port2 = ntohs(addr->sin_port);
    char ip[16];
    memset(ip, 0, sizeof(ip));
    inet_ntop(AF_INET, &addr->sin_addr.s_addr, ip, sizeof(ip));
    printf("server: (client address: %s(%d) connected)\n", ip, port2);
}

void do_service(int cfd){
    long t = time(0);
    char* s = ctime(&t);
    size_t size = strlen(s) * sizeof(char);
    if( write(cfd, s, size) != size)
        perror("write error");
}

int fd;
void sig_handler(int sig){
    if(sig == SIGINT){
        close(fd);
        exit(1);
    }
}

int main(void){
    if(signal(SIGINT, sig_handler) == SIG_ERR){
        perror("signal sigint error");
        exit(1);
    }

    //第一步 创建socket 
    //AF_INET: IPV4
    //SOCK_STREAM: tcp
    //0: 默认协议
    if((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        perror("socket create error");
    printf("server: socket created\n");

    //这两行解决 Bind error: Address already in use
    //可以使绑定的ip关闭后立刻重新使用
    int on = 1;
    int ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

    //第二步 调用bind 将socket与地址端口绑定
    struct sockaddr_in  sock_addr;
    memset(&sock_addr, 0, sizeof(sock_addr));
    sock_addr.sin_family = AF_INET;
    int port = 12345;
    sock_addr.sin_port = htons(port); //主机字节序转为网络字节序
    sock_addr.sin_addr.s_addr = INADDR_ANY;
    if(bind(fd, (struct sockaddr*)&sock_addr, sizeof(sock_addr)) < 0)
        perror("bind error");
    printf("server: bind OK\n");

    //第三步 调用listen启动监听(指定port监听)
    //通知系统去接受来自客户端的连接请求
    //(将接受到的客户端连接放置到长度为10的队列中)
    if(listen(fd, 10) < 0)
        perror("listen error");
    printf("server: listen OK\n");

    //第四步 调用accept函数从队列中获得一个客户端的请求连接
    //并返回一个客户端的socket文件描述符
    //如果没有客户端连接请求, 到这里会阻塞
    //第二个参数用来获得客户端的地址结构
    int client_fd;
    struct sockaddr new_addr;
    int len = sizeof(new_addr);
    if( (client_fd = accept(fd, &new_addr, &len)) < 0 )
        perror("accept error");
    printf("server: accept  OK\n");

    //应答  (读客户端数据, 写数据给客户端)
    print((struct sockaddr_in*)&new_addr);
    do_service(client_fd);

    //关闭socket文件
    close(fd); 
    printf("server: close OK\n");   
    return 0;
}

编译过后打开服务器:

这里写图片描述

很显然服务器进程当前是阻塞状态(accept), 等待客户端的连接

连接服务器的方式有很多种, 这里我的服务器进程是在桥接的虚拟机中

比如我们可以在本机中打开浏览器用http访问它:

这里写图片描述

下面是服务器进程得到的信息:

这里写图片描述

当然为了学习 我们还得完成tcp模型中客户端进程的代码:

//tcp_client.c
#include <netdb.h>
#include <unistd.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <memory.h>

const int port = 12345;
const char* ipaddr = "192.168.1.209";

int main(void){

    int fd;
    if((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        perror("socket create error");
    printf("client: socket created\n");

    struct sockaddr_in  serveraddr;
    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;

    //主机字节序改网络字节序
    //host to network
    serveraddr.sin_port = htons(port);
    //pointer to network
    inet_pton(AF_INET, ipaddr, &serveraddr.sin_addr.s_addr);
    //调用connect指定服务器的ip
    if(connect(fd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0)
        perror("connect error");
    printf("client: connect OK\n");

    char buffer[1024];
    memset(buffer, 0, sizeof(buffer));
    size_t size;
    if((size = read(fd, buffer, sizeof(buffer))) < 0)
        perror("read error");
    printf("client read content: %s", buffer);

    close(fd);

    return 0;
}

服务端进程及 客户端进程连接后的显示:

这里写图片描述

这里写图片描述

好了到此为止 , 我们的案例就完成了..


转载请注明出处:

CSDN_BLOG : AXuanK

猜你喜欢

转载自blog.csdn.net/AXuan_K/article/details/52294495