Linux 网络编程——socket 网络编程

文章目录

  • 网络基础
    • TCP/UDP对比
    • TCP/IP协议族体系
    • socket
    • 端口号作用
    • 字节序
      • 字节序转换api
  • TCP 编程 API
    • 服务端开发步骤
    • 服务端开发函数
      • 创建套接字:
      • 为套接字添加信息
      • 监听:
      • 连接:
      • 测试连接
      • 数据交互第一套API
      • 数据交互的第二套API
    • 客户端开发步骤
    • 客户端开发函数
  • UDP 编程
    • bind:
    • receivefrom:
    • sendto:
    • send 函数
    • recv 函数
  • 并发服务器
  • 实现双方聊天

网络基础

  • 多进程之间的通信通过内核,而多机通信需要使用网络
  • 数据传输:协议,即数据格式
  • 每个数据包都必须携带目的IP地址和源IP地址,路由器依靠此信息为数据包选择路由

TCP/UDP对比

  1. TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前,不需 要建立连接
  2. TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付
  3. TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的
    UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)
  4. 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
  5. TCP首部开销20字节;UDP的首部开销小,只有8个字节
  6. TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道

TCP/IP协议族体系

TCP/IP协议族体系是Internet事实上的工业标准。
一共有四层

应用层 Relnet,FTP,HTTP,DNS,SMTP等
传输层 TCP和UDP
网络层 IP,ICMP和IGMP,端到端传输
网络接口和物理层 以太网,令牌环网,FDDI,wifi,gps/2G/3G/4G,驱动(屏蔽硬件差异)

socket

  • 是一个编程接口,是一个特殊的文件描述符(对他执行IO的操作函数,比如read,write,close等),并不仅限于TCP/IP协议,面向连接TCP,无连接UDP。

  • socket代表网络编程的一种资源

分类:

  1. 流式套接字(SOCK_STREAM)。唯一对应 TCP 提供了一个面向连接,可靠的数据传输服务,数据无差错,无重复的发送顺序接收。内射击流量控制,避免数据流淹没慢的接收方。数据被看作式字节流,无长度限制。
  2. 数据包套接字(SOCK_DGRAM)。唯一对应UDP提供无连接服务器,数据包以独立数据包的形式被发送,不提供无差错保证,数据可能丢失或重复,顺序发送,可能乱序接收。
  3. 原始套接字(SOCK_RAW)。对应多个协议,发送穿透了传输层可以对较低层次协议(网络层)如IP,ICMP直接访问。

端口号作用

  1. 一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等。这些服务完全可以通过1个IP地址来实现。那么,主机是怎样区分不同的网络服务呢?显然不能只靠IP地址,因为IP 地址与网络服务的关系是一对多的关系。
  2. 实际上是通过“IP地址+端口号”来区 分不同的服务的。端口提供了一种访问通道,服务器一般都是通过知名端口号来识别的。例如,对于每个TCP/IP实现来说,FTP服务器的TCP端口号都是21,每个Telnet服务器的TCP端口号都是23,每个TFTP(简单文件传送协议)服务器的UDP端口号都是69。
  3. 16位数字,1-65535,
  4. 为了区分一台主机接收到的数据包应该转交给哪个任务进程处理,使用端口号来区别
  5. 预留端口,1-1023(FTP:24, SSH:22, HTTP: 80 ,HTTPS :469)
  6. 保留端口:1024-5000(不建议使用)
  7. 可以使用的端口: 5000~65535
  8. TCP端口号于UDP端口号独立
  9. 网络里的通信是由 IP地址+端口号 来决定的
  10. 端口号需要传递到网络上,需要使用api转化字节序 htons()

字节序

定义:字节序是指多字节数据在计算机内存中存储或网络传输时各字节的存储顺序

常见的字节序:

  • Little endian 小端字节序 将低序字节存储在起始地址
  • Big endian 大端字节序 将高序字节存储在起始地址

字节序是指不同的CPU访问内存中的多字节数据时候,存在大小端的问题

  • 网络字节序=大端字节序
  • x86系列CPU都是小端字节序
  • power/miop:arm作为路由时,大端模式
  • 如果CPU访问的是字符串,则不存在大小端问题

字节序转换api

#include 

uint16_t htons(uint16_t host16bitvalue);    //返回网络字节序的值
uint32_t htonl(uint32_t host32bitvalue);    //返回网络字节序的值
uint16_t ntohs(uint16_t net16bitvalue);     //返回主机字节序的值
uint32_t ntohl(uint32_t net32bitvalue);     //返回主机字节序的值

h代表host,n代表net,s代表short(两个字节),l代表long4个字节),
通过上面的4个函数可以实现主机字节序和网络字节序之间的转换。
有时可以用INADDR_ANY,INADDR_ANY指定地址让操作系统自己获取

TCP 编程 API

服务端开发步骤

  1. 创建套接字:socket()返回值是套接字的描述符
  2. 为套接字添加信息(IP地址和端口号):bind()
  3. 监听网络连接:listen()
  4. 监听到有客户端接入,接收一个连接:accept()
  5. 数据交互:read()和write()
  6. 关闭套接字,断开连接:close()

Linux 网络编程——socket 网络编程_第1张图片

Linux 网络编程——socket 网络编程_第2张图片

服务端开发函数

创建套接字:

函数原型:

int socket(int domain, int type, int protocol);

参数:

Linux 网络编程——socket 网络编程_第3张图片
返回值:

  • 成功则返回文件描述符
  • 失败则返回-1

注意:

  • 如果是IPV6编程,要使用struct sockddr_in6结构体(man 7 IPV6),通常使用struct sockaddr_storage来编程。

为套接字添加信息

函数原型:

int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen);

参数:

在这里插入图片描述

  • addrlen:第三个参数为结构体的长度,使用sizeof(struct sockaddr_in)

注意:

  • 第二个参数需要强制类型装换为(struct sockaddr*),因为addr指针变量是由结构体struct sockaddr_in 创建的

  • 使用sockaddr_in结构体替换sockaddr结构体:

Linux 网络编程——socket 网络编程_第4张图片

  • 协议族选:AF_INET

  • 第三个参数为结构体,配置结构体变量in_addr的成员s_addr,调用inet_aton()方法将其转化为网络字节序

struct in_addr{
    uint32_t s_addr;
}

实例:

#include 
#include 
int inet_aton(const char* straddr,struct in_addr *addrp);
//把字符串形式的“192.168.1.123”转为网络能识别的格式

例如:在bind函数的结构体中使用
struct sokeaddr_in s_addr;
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(8989);//端口号需要转化
inet_aton("127.0.0.1",&s_addr.sin_addr)
//第二个参数结构体指针,指向in_addr 结构体,所以取地址取到该结构体即可
        
char* inet_ntoa(struct in_addr inaddr); 
//把网络格式的ip地址转为字符串形式

linux中查询该结构体定义的位置:

cd /user/include/

grep “struck sockaddr_in {” * -nir

大括号可以搜索到声明的地方

*是当前目录下

r是递归寻找

n是显示行号

i是不区分大小写

头文件是

vi linux/in.h + 184

监听:

函数原型:

int listen(int sockfd,int backlog);

参数:

Linux 网络编程——socket 网络编程_第5张图片
返回值:

  • 成功返回0
  • 失败返回-1

注意:

  • 第二个参数一般填写5
  • 内核中服务器的套接字fd会维护2个链表
    1. 正在三次握手的客户端链表(数量=2*backlog+1)
    2. 已经建立好连接的客户端链表(已经完成三次握手分配好了的newfd)

连接:

  • 当服务器开启监听,内核会维护两个队列,此时accept会查询队列中完成三次握手的客户机,若有则与其连接,返回新的套接字描述符

函数原型:

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

返回值:

  • 是新的套接字描述符,用于后续读写操作,因为原先的套接字描述符可能被多个客户申请连接使用

参数与功能:

Linux 网络编程——socket 网络编程_第6张图片
注意:

  • 第二三个参数是客户端的信息,如果不需要获取则可以为NULL。
  • 如果accept第二三个参数都有,则可以用ip地址转换api打印客户机的ip地址
printf("%s\n",inet_ntoa(c_addr.sin_addr));
  • 第二个参数与bind函数一样,所以需要使用memset函数进行数据清空
#include 
struct sockaddr_in s_addr;
struct sockaddr_in c_addr;
memset(&s_addr,0,sizeof(struct sockaddr_in));
memset(&c_addr,0,sizeof(struct sockaddr_in));
  • 第二个参数需要强制类型装换为(struct sockaddr*)

测试连接

在window的控制台输入
ping 服务器地址或telnet 服务器地址 端口号

数据交互第一套API

函数原型:

ssize_t write(int fd,const void *buf,size_t nbytes);
ssize_t read(int fd,void *buf,size_t nbyte);
//函数返回值为读写的字节个数,错误则返回-1
//返回0表示客户端退出

注意:

  • 此时的fd为accept的返回值,而不是socket()返回值

  • 在套接字通信中进行字节读取函数与IO读取的略语有区别,因为他们输入或输出的字节数比请求的少

  • 网络I/O操作:(一)read()/write()(二)recv()/send()(三)readv()/writev()(四)recvmsg()/sendmsg()(五)recvfrom()/sendto()

数据交互的第二套API

  1. 在TCP套接字上发送数据函数:有连接
ssize_t send(int s,const void *msg,size_t len,int flags);

参数:

Linux 网络编程——socket 网络编程_第7张图片

  1. 在TCP套接字上接收数据函数:有连接
ssize_t recv(int s,void *buf,size_t len,int flags);

Linux 网络编程——socket 网络编程_第8张图片

客户端开发步骤

客户端开发函数

函数原型:

int connect(int sockfd,const struct sockaddr *addr,socklen_t addrlen);

参数:

Linux 网络编程——socket 网络编程_第9张图片

注意:

  • 头文件中 会与 冲突
  • connect和accept都会阻塞

UDP 编程

Linux 网络编程——socket 网络编程_第10张图片

bind:

  • 绑定服务器:TCP地址和端口号

receivefrom:

  • 阻塞等待客户端数据

sendto:

  • 指定服务器的IP地址和端口号,要发送的数据

send 函数

ssize_t send(int sockfd,const void *buf,size_t len,int flags);
 
参数:
    sockfd:socket函数返回的fd
    buffer:发送缓冲区首地址
    length:发送的字节
    flags:发送方式(通常为0),作用和write一样
        MSG_DONTWAIT,非阻塞
        MSG_OOB:用于TCP类型的带外数据(out of band)
 
返回值:
    成功:实际发送的字节数
    失败:-1,并设置errno

recv 函数

int recv( SOCKET s, char FAR *buf, int len, int flags);
 
    flag:一般填0,和read作用一样
        特殊的标志:
        MSG_DONTWAIT
        MSG_OOB:读取带外数据
        MSG_PEEK:

并发服务器

1.TCP多线程服务器
2.TCP多进程并发服务器

Linux 网络编程——socket 网络编程_第11张图片

实现双方聊天

在网络进程中,父进程等待客户端的服务请求,当这种请求到达时,父进程调用fork,使得子进程处理该请求,父进程继续等待下一个服务请求的到达

服务端

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main (int argc,char ** argv){

        int s_fd;
        int c_fd;
        int n_read;
        char readBuf[128];
        char msg[128]={0};

        struct sockaddr_in s_addr;
        struct sockaddr_in c_addr;

        if(argc !=3){
                printf("param is not enough");
                exit(-1);
        }

        memset(&s_addr,0,sizeof(struct sockaddr_in));
        memset(&c_addr,0,sizeof(struct sockaddr_in));

        s_fd = socket(AF_INET,SOCK_STREAM,0);
        if(s_fd ==-1){
              perror("socket");
                exit(-1);

        }

        s_addr.sin_family =AF_INET;
        s_addr.sin_port = htons(atoi(argv[2]));
        inet_aton(argv[1],&s_addr.sin_addr);

        bind(s_fd,(struct sockaddr*)&s_addr,sizeof(struct sockaddr_in));

        listen(s_fd,10);

        int clen = sizeof(struct sockaddr_in);
        while(1){
                c_fd = accept(s_fd,(struct sockaddr*)&c_addr,&clen);
                if(c_fd == -1 ){

                        perror("accept");
                }

                if(fork()==0){
                        if(fork()==0){
                                while(1){
                                        memset(msg,0,sizeof(msg));
                                        printf("input:");
                                        fgets(msg,128,stdin);
                                        write(c_fd,msg,strlen(msg));
                                }
                        }
                        while(1){
                                memset(readBuf,0,sizeof(readBuf));
								n_read = read(c_fd,readBuf,128);
                                if(n_read == -1){
                                        perror("read");
                                }else{
                                        printf("get message from client:%s\n",readBuf);
                                }

                        }
                        break;
                }

        }
        return 0;
}                            

客户端

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main (int argc,char ** argv){

        int c_fd;
        int n_read;
        char readBuf[128];
        char msg[128]={0};
    
        struct sockaddr_in c_addr;
        memset(&c_addr,0,sizeof(struct sockaddr_in));

        if(argc !=3){
                printf("param is not enough");
                exit(-1);
        }

        c_fd = socket(AF_INET,SOCK_STREAM,0);
        if(c_fd ==-1){
                perror("socket");
                exit(-1);
        }
        c_addr.sin_family =AF_INET;
        c_addr.sin_port = htons(atoi(argv[2]));
        inet_aton(argv[1],&c_addr.sin_addr);

        if(connect(c_fd,(struct sockaddr*)&c_addr,sizeof(struct sockaddr))==-1){
                perror("connect");
                exit(-1);
        }
        while(1){
                if(fork()==0){
                        while(1){
                                memset(msg,0,sizeof(msg));
                                printf("input:");
                                fgets(msg,128,stdin);
                                write(c_fd,msg,strlen(msg));
                        }
                }
                while(1){
                        memset(readBuf,0,sizeof(readBuf));
                        n_read = read(c_fd,readBuf,128);
                        if(n_read == -1){
                                perror("read");
                        }else{
                                printf("get message from server:%d,%s\n",n_read,readBuf);   
                        }
                }
                break;

        }
        return 0;
}


注意:

  • gcc并没有禁止gets. 只是会提示warning。如果设置了把所有warning处理为error才会导致无法使用。可以用fgets来代替。fgets(s, max_len, stdin);等效于gets(s)
    • char s[200];
    • fgets(s, 200, stdin);

优秀博文:
https://blog.51cto.com/u_15467009/4876970
https://www.cnblogs.com/MyLove-Summer/p/5215287.html

你可能感兴趣的:(网络,linux,udp)