Linux网络编程——简单基于TCP协议的服务器/客户端示例

说明
  本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知,马上删帖致歉。
  QQ 群 号:513683159 【相互学习】
内容来源
  《Linux网络编程》

功能描述:

  客户端连接服务器后从标准输入读取字符串发送给服务器。
  服务器接收到字符串后,发送接收到的总字符串个数给客户端、
  客户端将接受到的服务器信息打印到标准输出。
  整个过程流程如下:
在这里插入图片描述

源文件

服务器端源文件:tcp_server.c

/**
 *   Step 1 : 初始化工作
 *      1.头文件
 *      2.宏定义:侦听端口地址与侦听队列长度
 *      3.函数声明:服务器对客户端的处理函数,位于:tcp_process.c
 *      4.变量:定义并初始化
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>          
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>

#define PORT 8888                                    //侦听端口地址:8888
#define BACKLOG 2                                    //侦听队列长度:2

extern void process_conn_server(int s);              //服务器对客户端的处理:读取数据并发送响应字符  

int main(int argc,char *argv[])
{
	int ss = 0;                                      //ss = server socket = 服务器socket描述符
    int cs = 0;                                      //cs = client socket = 客户端socket描述符
    struct sockaddr_in server_addr;                  //服务器地址结构
    struct sockaddr_in client_addr;                  //客户端地址结构
    int ret = 0;                                     //返回值
    pid_t pid;                                       //进程ID

    /**
     *  Step 2 : 建立套接字
    */
    ss = socket(AF_INET,SOCK_STREAM,0);              //创建一个AF_INET族的流类型socket
    if(ss < 0)                                       //检查是否正常创建socket
    {
        perror("socket errorn");
        exit(EXIT_FAILURE);
    }
    
    /**
     *  Step 3 : 设置服务器地址
     *  Note: 
     *      htonl():将主机数转换成无符号长整型的网络字节顺序
     *      htons():将整型变量从主机字节顺序转变成网络字节顺序
    */
    bzero(&server_addr,sizeof(server_addr));          //清零
    server_addr.sin_family = AF_INET;                 //设置地址族为AF_INET
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);  //本地地址
	server_addr.sin_port = htons(PORT);               //设置端口号 
	   
    /**
     *  Step 4 : 绑定地址结构到套接字描述符
    */
    ret = bind(ss,(struct sockaddr*)&server_addr,sizeof(server_addr));
    if (ret < 0)                                      //出错
    {
        perror("bind errorn");
        exit(EXIT_FAILURE);
    }

    /**
     *  Step 5 : 设置侦听,侦听队列长度为2,可同时处理两个客户端连接请求
    */
    ret = listen(ss,BACKLOG);
    if (ret < 0)                                      //出错
    {
        perror("bind errorn");
        exit(EXIT_FAILURE);
    }

    /**
     *  Step 6 : 主循环过程
    */
    for(;;)
    {
        /* 接收客户端连接 */
        int addrlen = sizeof(struct sockaddr);
        cs = accept(ss,(struct sockaddr*)&client_addr,&addrlen);
        if(cs < 0)                                    //出错
        {
            continue;                                 //结束本次循环
        }

        /* 建立一个新的进程处理到来的连接 */
        pid = fork();                                 //创建新进程
        if(pid < 0)         /* pid < 0,fork失败 */
        {
            printf("fork errorn");
        }
        else if(pid == 0)   /* pid = 0,子进程 */
        {
            close(ss);                                //子进程中关闭服务器的侦听
            process_conn_server(cs);                  //处理连接                             
        }
        else                /* pid > 0,父进程 */            
        {
            close(cs);                                //父进程中关闭客户端的连接
        }
    }
	return 0;
}

客户端源文件:tcp_client.c

/**
 *   Step 1 : 初始化工作
 *      1.头文件
 *      2.宏定义:侦听端口地址
 *      3.函数声明:服务器对客户端的处理函数,位于:tcp_process.c
 *      4.变量:定义并初始化
 */

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

#define PORT 8888                                     //端口地址:8888
 
extern void process_conn_client(int s);

int main(int argc,char *argv[])
{
    int s = 0;                                        //socket描述符        
    struct sockaddr_in server_addr;                   //服务器地址结构  
    int ret = 0;                                      //返回值

    /**
     *  Step 2 : 建立套接字
     */
    s = socket(AF_INET,SOCK_STREAM,0);                //创建一个AF_INET族的流类型socket
    if(s < 0)                                         //检查是否正常创建socket
    {
        perror("socket errorn");
        exit(EXIT_FAILURE);
    }

    /**
     *  Step 3 : 设置服务器地址
     */
    bzero(&server_addr,sizeof(server_addr));          //清零
    server_addr.sin_family = AF_INET;                 //设置地址族为AF_INET
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);  //本地地址
	server_addr.sin_port = htons(PORT);               //设置端口号 

    /**
     *  Step 4 : 将用户输入的字符串类型的IP地址转为整型
     */
    inet_pton(AF_INET,argv[1],&server_addr.sin_addr);

    /**
     *  Step 5 : 连接服务器
     */
    connect(s,(struct sockaddr*)&server_addr,sizeof(struct sockaddr));
    process_conn_client(s);                           //客户端处理过程
    close(s);                                         //关闭连接
}

处理源文件:tcp_process.c

#include <string.h>
#include <stdio.h>
#include <unistd.h>


/*  服务器对客户端处理函数*/
void process_conn_server(int s)
{
    ssize_t size = 0;
    char buffer[1024];                              //数据的缓冲区

    for (;;)                                        //循环处理过程
    {
        size = read(s,buffer,1024);                 //从套接字中读取数据放到缓冲区buffer中

        if(size == 0)                               //没有数据
        {
            return;
        }

        /* 构建响应字符,为接收到客户端字节的数量 */
        sprintf(buffer,"%ld bytes altogethern",size);
        write(s,buffer,strlen(buffer)+1);           //发给客户端
    }
    
}


/* 客户端的处理过程 */
void process_conn_client(int s)
{
    ssize_t size = 0;
    char buffer[1024];                                //数据的缓冲区

    for(;;)
    {
        /* 从标准输入中读取数据放到缓冲区buffer中 */
        size = read(0,buffer,1024);
        if(size > 0)                                  //读到数据
        {
            write(s,buffer,size);                     //发送给服务器
            size = read(s,buffer,1024);               //从服务器读取数据
            write(1,buffer,size);                     //写到标准输出,0表示标准输入,1表示标准输出
        }
    }
}

Makefile文件

all:client server												#all规则,依赖于client和server规则

client:tcp_process.o tcp_client.o								#client规则,生成客户端可执行程序
        gcc -o client tcp_process.o tcp_client.o
server:tcp_process.o tcp_server.o								#server规则,生成服务器端可执行程序
        gcc -o server tcp_process.o tcp_server.o
clean:															#清理规则,删除client、server和中间文件
        rm -f client server *.o

编译并运行

  将上面文件全部置于同一目录下。
  1️⃣编译
    执行命令:make,结果如下:

cc    -c -o tcp_process.o tcp_process.c					#生成tcp_process.o中间文件
cc    -c -o tcp_client.o tcp_client.c					#生成tcp_client.o中间文件
gcc -o client tcp_process.o tcp_client.o				#由tcp_process.o与tcp_client.o生成可执行文件:client 
cc    -c -o tcp_server.o tcp_server.c					#生成tcp_server.o中间文件
gcc -o server tcp_process.o tcp_server.o				#由tcp_process.o与tcp_server.o生成可执行文件:server 

  2️⃣运行
    ①打开终端,执行命令:./server
    会执行服务端可执行程序server,该程序会在端口8888上侦听,等待客户端的连接请求。
    ②另起终端,执行命令:./client 127.0.0.1
    会执行服务端可执行程序client ,该程序会连接上服务器
    紧接着输入字符串就会有相应,效果如下:

[email protected]:/home/hhb/桌面/tcp$ ./client 127.0.0.1
nihao
6 bytes altogether
shiyishi
9 bytes altogether

查询网络连接情况

  在程序正常运行的状态,再另起终端,使用netstat命令查询网络连接情况。
  执行指令:netstat,效果如下:

激活Internet连接 (w/o 服务器)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 localhost:39460         localhost:8888          ESTABLISHED
tcp        0      0 localhost:8888          localhost:39460         ESTABLISHED

  其中8888为服务器端的端口,39460为客户端的端口
  服务器和客户端段通过这两个端口建立了连接。

服务器端与客户端程序设计模式

在这里插入图片描述

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>