【学习笔记】linux简单C/S网络模型实现及重点知识总结(C语言实现)

实现功能

简单的C/S模型,实现小写转大写的功能


代码

客户端:

#include
#include
#include
#include
#include
#include
#include
#include
#define MAXLEN 64
#define PORT 2344
int main()
{
    struct sockaddr_in serveraddr;
    int conncetfd;
    char buf[MAXLEN] = "";
    conncetfd = socket(AF_INET, SOCK_STREAM, 0);
    //将结构体初始化,避免脏数据
    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;        //协议族:IPV4协议
    //htons(host_to_net_short)将16位的本地数据转换成16位网络数据(大小端转换)
    serveraddr.sin_port = htons(PORT);      //端口
    //将字符串转换成网络IPV4地址格式并存入serveraddr.sin_addr中
    inet_pton(AF_INET, "127.0.0.1", &serveraddr.sin_addr);
    //发起连接,连接成功前阻塞
    connect(conncetfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));

    //循环读取字符串,并交给服务器处理
    while(~scanf("%s", buf))
    {
        //遇到quit则退出循环
        if(!strncmp("quit", buf, 4)) break;
        write(conncetfd, buf, strlen(buf));
        int retlen = read(conncetfd, buf, MAXLEN);
        write(1, buf, retlen);
    }

    //关闭连接,给服务端发送quit让服务端关闭连接
    write(conncetfd, "quit", strlen(buf));
    close(conncetfd);
    return 0;
}

服务端:

#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PORT 2344
#define MAXLEN 64
void myperror(char* msg)
{
    perror(msg);
    exit(1);
}
int main()
{
	//服务端从listen之前的操作基本与客户端同理
    struct sockaddr_in serveraddr, clientaddr;
    int listenfd, conncetfd, ret_status;
    char buf[MAXLEN];
    socklen_t client_addrlen;
    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if(listenfd < 0) myperror("socket");

    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(PORT);
    serveraddr.sin_addr.s_addr = INADDR_ANY;
    ret_status = bind(listenfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
    if(ret_status < 0) myperror("bind");

	//对 listenfd 进行监听
    ret_status = listen(listenfd, 3);
    if(ret_status < 0) myperror("listen");

    while(1)
    {
    	//监听到新连接,调用accept完成握手,进行数据传输操作
        client_addrlen = sizeof(clientaddr);
        conncetfd = accept(listenfd, (struct sockaddr *) &clientaddr,
                           &client_addrlen);
        if(conncetfd < 0) myperror("accept");

        char client_addrstr[17];
        int retlen;
        //后面都是数据传输操作,IO方面知识
        while((retlen = read(conncetfd, buf, MAXLEN)) > 0)
        {
            //收到客户端发送的quit信号,关闭连接,结束程序
            if(!strncmp("quit", buf, 4)) break;
            printf("received from %s:%d\n",
                   inet_ntop(AF_INET, &clientaddr.sin_addr, client_addrstr, 16),
                   ntohs(clientaddr.sin_port));
            for(int i = 0; i < retlen; i++)
                buf[i] = toupper(buf[i]);

            ret_status = write(conncetfd, buf, retlen);
            if(ret_status < 0) myperror("write");
        }

        if(retlen < 0) myperror("read");
        close(conncetfd);
        printf("client close from %s:%d\n",
                inet_ntop(AF_INET, &clientaddr.sin_addr, client_addrstr, 16),
                ntohs(clientaddr.sin_port));

    }
    return 0;
}

运行截图

服务端:
【学习笔记】linux简单C/S网络模型实现及重点知识总结(C语言实现)_第1张图片

客户端:
【学习笔记】linux简单C/S网络模型实现及重点知识总结(C语言实现)_第2张图片


重难点及相关知识

1.客户端服务端连接过程

【学习笔记】linux简单C/S网络模型实现及重点知识总结(C语言实现)_第3张图片

  • 服务端和客户端初始化 socket ,得到⽂件描述符;

  • 服务端调⽤ bind ,将绑定在 IP 地址和端⼝;

  • 服务端调⽤ listen ,进⾏监听;

  • 服务端调⽤ accept ,等待客户端连接;

  • 客户端调⽤ connect ,向服务器端的地址和端⼝发起连接请求;

  • 服务端 accept 返回⽤于传输的 socket 的⽂件描述符;

  • 客户端调⽤ write 写⼊数据;服务端调⽤ read 读取数据;

  • 客户端断开连接时,会调⽤ close ,那么服务端 read 读取数据的时候,就会读取到了 EOF ,待处理完数据后,服务端调⽤ close ,表示连接关闭。这里我们特殊处理读到quit就可以关闭连接了。

这⾥需要注意的是,服务端调⽤ accept 时,连接成功了会返回⼀个已完成连接的 socket,后续⽤来传输数据。

2. struct sockaddrstruct sockaddr_instruct sockaddr_un

struct sockaddr 是通用的套接字地址,使用系统调用如bind的时候其中一个参数的参数类型就是这个。

struct sockaddr
{
	unsigned short sa_family;          /* 协议族, AF_xxx */
	char sa_data[14];                  /* 14字节的协议地址 */
};

struct sockaddr_in 则是 internet 环境下套接字的地址形式,二者长度一样,都是16个字节。二者是并列结构,指向 sockaddr_in 结构的指针也可以指向 sockaddr 。一般情况下,需要把 sockaddr_in 结构强制转换成 sockaddr 结构再传入系统调用函数中,比如bind。

struct sockaddr_in {
	short int sin_family;               /* 地址族 */
	unsigned short int sin_port;        /* 端口号 */
	struct in_addr sin_addr;            /* 网络地址 */
	unsigned char sin_zero[8];          /* 八字节的保留位 */
};

struct sockaddr_un 用于本地 socket 结构,结构如下,用法和struct sockaddr_in 类似。

struct sockaddr_un
{
	sa_family_t sun_family;              /*地址族,PF_UNIX或AF_UNIX */
	char        sun_path[UNIX_PATH_MAX]; /* 路径名 */
};

如图所示:
【学习笔记】linux简单C/S网络模型实现及重点知识总结(C语言实现)_第4张图片

3.htons()htonl()ntohs()ntohl()

htonl() – “Host to Network Long” – “对32位数据进行本地到网络的字节序转换”
ntohl() – “Network to Host Long” – “对32位数据进行网络到本地的字节序转换”
htons() – “Host to Network Short” – “对16位数据进行本地到网络的字节序转换”
ntohs() – “Network to Host Short” – “对16位数据进行网络到本地的字节序转换”

因为Linux大多是小端,而网络上的机器都是大端,在网络上发送数据需要进行字节序转换,否则可能会发生通信异常。

4.inet_ntop()inet_pton()inet_addr()

(1)int inet_pton(int family, const char *strptr, void *addrptr);

将点分十进制的ip地址转化为用于网络传输的数值格式
返回值:若成功则为1,若输入不是有效的表达式则为0,若出错则为-1

(2)const char * inet_ntop(int family, const void *addrptr, char *strptr, size_t len);

将数值格式转化为点分十进制的ip地址格式
返回值:若成功则为指向结构的指针,若出错则为NULL

这两个函数是随IPv6出现的函数,对于IPv4地址和IPv6地址都适用,函数中p和n分别代表表达(presentation)和数值(numeric)。地址的表达格式通常是ASCII字符串,数值格式则是存放到套接字地址结构的二进制值。

(3) in_addr_t inet_addr(const char *cp);

inet_addr 函数转换网络主机地址(如192.168.1.10)为网络字节序二进制值,如果参数char *cp无效,函数返回-1( INADDR_NONE ),这个函数在处理地址为255.255.255.255时也返回-1,255.255.255.255是一个有效的地址,不过inet_addr无法处理


参考资料

  1. 小林coding —— 图解网络

  2. inet_pton()和inet_ntop()函数详解

  3. struct sockaddr struct sockaddr_in struct sockaddr_un 结构详解

你可能感兴趣的:(linux网络编程,c语言,网络,服务器)