Linux网络编程基础

1. 网络编程基本概念

1.1 IP地址

IP地址本质其实就是一个4字节的无符号整型数。IP地址分为网络标识主机标识两部分组成。

  • 网络标识:标识主机所在的网络
  • 主机标识:标识主机的具体地址

根据网络标识和主机标识所占的字节数不同,IP地址一共有5种类型,常用的有以下3种类型:

A类:网络标识占1字节,主机标识占3字节

B类:网络标识占2字节,主机标识占2字节

C类:网络标识占3字节,主机标识占1字节
在这里插入图片描述
特殊的IP地址:

  • 0.0.0.0 / 0 — 保留,常用于代表“缺省网络”
  • 127.0.0.0 / 8 (这里8其实就是用来表代表网络标识占8bit数据位) — 该地址是回环地址,用于本地软件回送测试
  • 255.255.255.255 / 32 — 广播地址

私有地址(不在公网使用的,只在内网使用):

  • 10.0.0.0 - 10.255.255.255 / 8
  • 172.16.0.0 - 172.31.255.255 / 32
  • 192.168.0.0 - 192.168.255.255 /24

1.2 子网掩码

子网掩码,也叫地址掩码。本质其实也是一个4字节无符号整型数。子网掩码一般不会单独存在,子网掩码的作用就是用来区分IP地址中的网络标识和主机标识的。例如:

IP地址:192.168.10.250

子网掩码:255.255.255.0

那么可以看出网络标识是:IP地址的前面3字节。主机标识只占1字节。

1.3 端口号

端口号是一个2字节的数据,其作用就是当设备进行网络通信时,用于标识具体连接的是该设备的哪一个应用程序。

1.4 域名

域名其实就是某个某个网站的网址,比如:www.baidu.com ,但是这个网址最终会指向某一个特定的IP地址。因为IP地址是一长串的数字,比较难记住,于是人们发明一串有意义的字符串用来代替这个IP地址。所以,域名可以看作是IP地址的别名,多个域名可以指向同一个IP地址。

但是我们访问某个网址时,最终都要转换为访问某一个特定的IP地址,这就需要域名服务器(DNS)提供帮助。

2. Linux下网络编程常用的API函数

函数原型功能描述
int socket(int domain, int type, int protocol);创建套接字,为网络连接做准备
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);连接指定地址的远程设备
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);服务端编程专用。作用是socket创建套接字成功后,需要对套接字进行地址和端口的绑定,才能进行数据的收发
int listen(int sockfd, int backlog);服务端编程专用。监听客户端的连接请求
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);服务端编程专用。用于接受客户端的连接请求,接收连接成功后,会返回客户端的套接字。如果没有客户端进行连接,那么服务端程序会阻塞在该函数,直到有客户端发起连接请求
ssize_t send(int sockfd, const void *buf, size_t len, int flags);发送数据到远程设备
ssize_t recv(int sockfd, void *buf, size_t len, int flags);接收远程设备发回的数据,数据暂存在buf这个缓冲区中
int close(int fd);关闭连接,销毁套接字
in_addr_t inet_addr(const char *cp);将IP字符串转换为符合网络字节序的整数
int inet_aton(const char *cp, struct in_addr *inp);功能和inet_addr函数一样。但是该函数如果转换成功返回1,失败返回0.使用更加的严谨
char *inet_ntoa(struct in_addr in);将符合网络字节序的整数地址转换为字符串形式

3. 服务端例程

服务端的程序是长期暴露于网络的,并等待客户端的连接,而客户端是主动发起连接请求的。所以区分一个程序是服务端还是客户端,就看该程序是否是主动发起连接的。

3.1 服务端编程的流程

对于TCP/IP协议,一般服务端的编程流程如下:

  1. 调用 socket 函数创建 socket(侦听socket。该socket只用于接收连接,并不进行实际的通信);
  2. 调用 bind 函数将 socket 绑定到某个IP和端口的二元组上;
  3. 调用 listen 函数开启侦听状态;
  4. 调用 accept 函数等待客户端连接,当有客户端连接时,accept函数会返回一个与客户端通信的socket(客户端socket);
  5. 基于新产生的 socket 调用 send 或 recv 函数开始与客户端进行数据交互;
  6. 通信结束后,调用 close 函数关闭侦听 socket;

3.2 服务端代码

下面我们来使用Linux提供的API函数,编写一个服务端的例程。该服务端例程完成如下的功能:

  1. 服务端持续监听客户端的连接;
  2. 服务端连接了客户端的请求后,接收客户端发送过来的数据,并原样返回去;
  3. 当服务端接收到客户端发送过来的"quit"字符串,那么服务端断开连接。

代码如下:

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

int main()
{
    int s_sock = 0;
    struct sockaddr_in s_addr = {0};
    int c_sock = 0;
    struct sockaddr_in c_addr = {0};        // 用于保存客户端的地址信息
    socklen_t c_addrlen = 0;
    int recv_len = 0;
    char recv_buf[64] = {0};

    s_sock = socket(PF_INET, SOCK_STREAM, 0);   

    if( s_sock == -1 )
    {
        printf("server socket error\n");
        return -1;
    }

    s_addr.sin_family = AF_INET;                // 使用IPV4协议族
    s_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听本机上所有的IP
    s_addr.sin_port = htons(8888);              // 指定端口号

    if(bind(s_sock, (const struct sockaddr*)&s_addr, sizeof(s_addr)) == -1)
    {
        printf("bind error\n");
        return -1;
    }

    if(listen(s_sock, 1) == -1)
    {
        printf("listen error\n");
        return -1;
    }

    printf("server start success\n");

    while( 1 )
    {
        c_addrlen = sizeof(c_addr);
        c_sock = accept(s_sock, (struct sockaddr*)&c_addr, &c_addrlen);

        if( c_sock == -1 )
        {
            printf("client accpet error\n");
            return -1;
        }

        printf("c_sock = %d\n", c_sock);

        while( 1 )
        {
            recv_len = recv(c_sock, recv_buf, sizeof(recv_buf), 0);

            printf("receive: %s\n", recv_buf);

            if( strcmp(recv_buf, "quit") != 0 )
            {// 把接收到的字符原样返回去
                send(c_sock, recv_buf, recv_len, 0);
            }
            else
            {// 接收到quit字符串,断开与客户端的连接
                break;
            }
        }
        close(c_sock);
    }

    close(s_sock);

    return 0;
}

4. 客户端例程

一般对于TCP/IP协议的客户端程序,编程流程如下:

  1. 调用 socket函数创建客户端 socket
  2. 调用 connect 函数尝试连接服务器
  3. 连接成功以后调用 send 或 recv 函数开始与服务器进行数据交互
  4. 通信结束后,调用 close 函数关闭socket

下面我们编写客户端例程,主要实现功能如下:

  1. 接收用户输入的字符串,然后把输入的字符串通过网络发送给服务端;
  2. 接收服务端的原样返回的数据,并且把接收到的数据打印出来。

代码如下:

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

int main()
{
    int sock = 0;
    struct sockaddr_in addr = {0};
    char recv_buf[128] = {0};
    char input[32] = {0};
    int recv_len = 0;

    sock = socket(PF_INET, SOCK_STREAM, 0);

    if( sock == -1 )
    {
        printf("socket error\n");
        return -1;
    }

    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("192.168.4.102");
    addr.sin_port = htons(8888);

    if( connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1 )
    {
        printf("connect error\n");
        return -1;
    }

    printf("connect success\n");

    while( 1 )
    {
        printf("input: ");

        scanf("%s", input);     // 获取用户输入的字符串

        send(sock, input, strlen(input) + 1, 0);

        recv_len = recv(sock, recv_buf, sizeof(recv_buf), 0);   // 接收服务端返回的数据

        if( recv_len > 0 )
        {// 把服务端返回的数据打印出来
            printf("receive: %s\n", recv_buf);
        }
        else
        {// 服务端没有数据返回,退出程序
            break;
        }
    }

    close(sock);

    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值