Linux下多任务编程(网络编程)

前言

        本文记录OSI7层模型、TCP\IP模型、socket在UDP、TCP使用。

网络

        网络:多个计算机之间相互通信
        网络协议:多个计算机之间通信用的语言(是有一定规范的)

OSI 7层模型

        应用层 表示层 会话层 传输层 网络层 链路层 物理层

面向应用应用层应用程序:FTP、E-mail、Telnet
面向服务表示层数据格式定义、数据转换/加密
会话层建立通信进程的逻辑名字与物理名字之间的联系
面向通信传输层差错处理/恢复,流量控制,提供可靠的数据传输
通信子网网络层数据分组、路由选择
链路层数据组成可发送、接收的帧
物理层传输物理信号、接口、信号形式、速率

TCP/IP协议

        四层(IBM公司推出的)

应用层http:超文本传输协议
ftp:文件传输协议
smtp:简易邮件传输协议
dhcp:动态ip分配协议
dns:域名解析协议(www.baidu.com->192.168.11.111)
传输层TCP:流式套接字
UDP:数据报套接字,可靠,但是慢
网络层

ip:网络层最重要的协议(IPV4、IPV6)
icmp:ping命令使用的协议(判断对方是否在线)
arp:地址解析协议(ip地址转换成物理地址)

网络接口层ether(以太网):常用的所有物理介质(网线、WIFI)

        基于TCP协议:http、ftp、smtp 保证数据不能丢失,允许慢一些。 
        基于UDP协议:dhcp、dns 数据可以丢,但是快

查看ip地址和物理地址的记录
arp -a

        物理地址(mac):网卡出厂时,自带编号,不可更改
        IP地址:由路由器分配的唯一标识,连接上不同的网络,IP地址也会发生改变

交换机和路由器的区别(重点)

        交换机:根据mac地址(物理地址)进行数据的交换,不能分配ip地址,仅限于局域网使用。
        路由器:根据IP地址进行数据的交互,能分配ip地址,作用于互联网和局域网。

网络管理相关命令(重点)

       (1) 查看ip地址

ifconfig

        (2)nc 模拟服务器

TCP通信
启动服务器
nc -l 8888
启动客户端
nc 127.0.0.1 8888 (整数1-65535之间)

UDP通信
启动接收端
nc -ul 9999
启动发送端
nc -u 127.0.0.1 9999

Linux下的网络编程

IP地址的存储方式

        (1)点分制        192.168.1.111        每个数的取值范围是0-255
        (2)整数形式        0xc0a8016f

sizeof("192.168.1.111");    //14个字节
sizeof(0xc0a8016f);         //4个字节

        区别:点分制可读性强,占用空间大;整数形式可读性差,占用空间小。

inet_addr函数

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp);

        功能: 将点分制(字符串)的IP地址转成整数的IP地址

        参数:点分制(字符串)的IP地址

        返回值:整数的IP地址

大端序和小端序

        大端序(网络字节序):低地址存高字节,高地址存低字节;
        小端序:低地址存低字节,高地址存高字节。

验证操作系统采用的是小端序还是大端序

指针方式验证

int main(int argc, char const *argv[])
{
    int a = 0xc0a8016f;
    char *p = (char *)&a;
    printf("%02x %p\n", *p, p);
    printf("%02x %p\n", *(p+1), p+1);
    printf("%02x %p\n", *(p+2), p+2);
    printf("%02x %p\n", *(p+3), p+3);
    return 0;
}

6f 0x7ffcbc0fde7c
01 0x7ffcbc0fde7d
a8 0x7ffcbc0fde7e
c0 0x7ffcbc0fde7f
验证之后我的主机是小端序

 共用体方式验证

union addr
{
    int a;
    char b[4];
};
int main(int argc, char const *argv[])
{
    union addr num;
    num.a = 0xc0a8016f;
    printf("%x\n",num.b[0]);
    return 0;
}

网络字节序和主机字节序

 h:host 主机        to:转        n:network 网络        l:long 四字节        s:short 二字节

htonl

主机转网络,转四字节变量

htons主机转网络,转二字节变量
ntohl网络转主机,转四字节
ntons网络转主机,转二字节

端口号 

         区分一台电脑上的应用程序的,也就是网络数据包应该给哪个应用。同一台电脑上,同一个端口号在同一时刻只能被一个应用程序绑定。

        一些有固定端口号的应用
        ftp        21
        http        80
        dns        53
        dhcp        67

        端口号(1-65535),1-1024分配给一些固定的服务

Socket 编程

        套接字编程,也叫网络编程,进程间通信中唯一一个可以跨主机的通信

UDP通信

        传输速度快,但是不可靠
        应用:局域网,对数据的实时性要求较高的互联网

#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

        参数1:族协议 通常为AF_INET(ipv4)
        参数2:SOCK_DGRAM 使用UDP Socket
                     SOCK_STREAM 使用TCP Socket
        参数3:网络层协议,为0即可

        返回值:≥0 Socket的文件描述符(open的返回值类似),<0 出错

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <stdio.h>
int main(int argc, char const *argv[])
{
    //int socket(int domain, int type, int protocol);
    int sock_fd = socket(AF_INET,SOCK_DGRAM,0);
    printf("%d\n",sock_fd);
    close(sock_fd);
    return 0;
}
接收端 
bind函数
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

recvfrom函数
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                 struct sockaddr *src_addr, socklen_t *addrlen);
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
int main(int argc, char const *argv[])
{
    // 1.创建套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    // 2.绑定自己的IP地址和端口号
    struct sockaddr_in myaddr;
    myaddr.sin_family = AF_INET;
    myaddr.sin_port = htons(8888);
    myaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    int ret = bind(sockfd, (struct sockaddr *)&myaddr, sizeof(struct sockaddr));
    // 3.接收数据
    char buf[128];
    struct sockaddr_in your_addr;
    int len = sizeof(your_addr);
    recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&your_addr, &len);
    printf("buf is %s\n", buf);
    // 4.关闭
    close(sockfd);
    return 0;
}
发送端 
sendto函数
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
               const struct sockaddr *dest_addr, socklen_t addrlen);
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
int main(int argc, char const *argv[])
{
    // 1.创建Socket
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    // 2.指定对方的ip地址和端口号
    struct sockaddr_in your_addr;
    your_addr.sin_family = AF_INET;
    your_addr.sin_port = htons(8888);
    your_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    // 3.发送数据
    char buf[128] = "hello";
    sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&your_addr, sizeof(your_addr));
    // 4.关闭
    close(sockfd);
    return 0;
}

 

 TCP通信

        可靠,但是慢

        完成可靠的方法:
        (1)发送之前,先建立连接;
        (2)发送时,有应答机制,如果中间数据丢失,会重新发送。

TCP通信流程

服务器端
socket函数(略)
bind函数(略)
listen函数
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);

        设置最大监听数(设置最大同时连接数):指正在连接的客户端,已经建立连接的客户端(在接收数据的客户端)不算在内。

        参数1:socket的返回值
        参数2:设置的最大监听数

accept函数
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

        阻塞等待客户端的连接

        参数1:socket的返回值;
        参数2:连接的客户端的ip地址和端口号的结构体的地址(一般为NULL,代表不关心,和recvfrom的最后两个参数一样);
        参数3:结构体长度的地址(一般为NULL)。

        返回值:newfd代表这个链接成功客户端的连接通道的编号;后续与这个客户端进行通信时,使用newfd。

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

        发送数据,如果无人发数据,则阻塞等待数据的发送

        参数1:accept函数的返回值
        参数2:发送数据的首地址
        参数3:缓存区长度
        参数4:0

        返回值:大于0 实际接收数据的长度;≤0 连接断开

实例代码
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
int main(int argc, char const *argv[])
{
    // 1.创建socket
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    // 2.绑定IP地址和端口号
    struct sockaddr_in myaddr;
    myaddr.sin_family = AF_INET;
    myaddr.sin_port = htons(8888);
    myaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    int ret = bind(sockfd, (struct sockaddr *)&myaddr, sizeof(myaddr));
    // 3.设置最大监听数
    listen(sockfd, 5);
    // 4.阻塞等待客户端的连接
    int newfd = accept(sockfd, NULL, NULL);
    // 5.接收
    char buf[128] = {0};
    ret = recv(newfd, buf, sizeof(buf), 0);
    printf("ret %d ,buf is %s\n", ret, buf);
    // 6.关闭
    close(newfd);
    close(sockfd);
    return 0;
}

客户端

1.创建socket
2.指定对方的ip地址和端口号
struct sockaddr_in client_addr;
client_addr.sin_family = AF_INET;
client_addr.sin_port = htons(atoi(argv[2]));
client_addr.sin_addr.s_addr = inet_addr(argv[1]);
3.连接客户端
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

        参数1:socket返回值
        参数2:结构体地址
        参数3:结构体长度

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

4.收发数据

        客户端只能连接一个服务器,所以没有newfd,所以recv/send的第一个参数用socket返回值

5.关闭close
实例代码
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <arpa/inet.h>
int main(int argc, char const *argv[])
{
    // 1.创建socket套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    // 2.指定对方的ip地址和端口号
    struct sockaddr_in your_addr;
    your_addr.sin_family = AF_INET;
    your_addr.sin_port = htons(8888);
    your_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    // 3.连接
    int ret = connect(sockfd, (struct sockaddr *)&your_addr, sizeof(your_addr));
    // 4.收发数据
    char buf[128] = {0};
    scanf("%s", buf);
    send(sockfd, buf, sizeof(buf), 0);
    // 5.关闭
    close(sockfd);
    return 0;
}

循环服务器

缺点:
        (1)newfd会被覆盖
        (2)accept和recv 会互相阻塞

        A客户端连接成功后,accept解除阻塞,代码运行到recv,等待A客户端发送的数据;若A一直不发送数据,会recv处一直阻塞,无法运行到accept,其它客户端无法进行连接。

TCP和UDP通信的区别

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值