linux下socket传输


—-socket传输—-

​ socket传输主要是为了解决不同进程间通信的问题。可分为 TCP 传输 和 UDP传输。 Tcp 是 面向连接的 可靠的 流式服务 。Udp 是 无连接 不可靠 数据报服务。

1. 什么是socket?

​ socket 称为“套接字”,它是计算机之间进行通信一种约定或一种方式。通过 socket 这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据。
 socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。
 socket就是该模式的一个实现:即socket是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)。
 socket()函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。

2. socket解决什么问题?

​ socket主要是用来解决网络跨进程通信(IPC)问题。 那网络之间是如何通信的呢?

3. 网络中进程之间如何通信?

​ 通信方式有本地的进程间通信网络进程间通信 两种。

3.1 本地的进程间通信

​ a、消息传递(管道、消息队列、FIFO)
​ b、同步(互斥量、条件变量、读写锁、文件和写记录锁、信号量)
   c、共享内存(匿名的和具名的)
   d、远程过程调用(RPC)

​ 本地可以通过进程PID来唯一标识一个进程。

3.2 网络间进程通信

​ 网络间进程是通过不同的主机间进行通信。因此首要问题如何标识一台主机,以及进一步如何标识一台主机上的唯一进程
​ TCP/IP协议族帮我们解决了这个问题,网络层的“ip地址”可以唯一标识网络中的主机,传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。

TCP/IP定义:是互联网相关的各类协议族的总称,如TCP,UDP,IP,FTP,HTTP等都属于TCP/IP族内的协议 。

OSI七层模型

osi七层模型

TCP/IP协议参考模型把所有的TCP/IP系列协议归类到四个抽象层中
 应用层:TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet 等等
 传输层:TCP,UDP
 网络层:IP,ICMP,OSPF,EIGRP,IGMP
 数据链路层:SLIP,CSLIP,PPP,MTU
 每一抽象层建立在低一层提供的服务上,并且为高一层提供服务。

osi协议

4. socket如何通信?

​ 网络中进程间利用三元组【ip地址,协议,端口】进行通信,而socket就是利用三元组解决网络通信的一个中间件工具。
​ socket通信的数据传输方式,常用的有两种:
​ a、SOCK_STREAM:表示面向连接的数据传输方式(TCP)。数据可以准确无误地到达另一台计算机,如果损坏或丢失,可以重新发送,但效率相对较慢。常见的 http 协议就使用 SOCK_STREAM 传输数据,因为要确保数据的正确性,否则网页不能正常解析。
b、SOCK_DGRAM:表示无连接的数据传输方式(UDP)。计算机只管传输数据,不作数据校验,如果数据在传输中损坏,或者没有到达另一台计算机,是没有办法补救的。也就是说,数据错了就错了,无法重传。因为 SOCK_DGRAM 所做的校验工作少,所以效率比 SOCK_STREAM 高。

5. 基于UDP的socket编程

​ UDP 是 无连接 不可靠 数据报服务
​ 所以UDP不需要进行三次握手来建立连接,直接发送数据,不保证可靠交付,不管对方收到还是没有收到。

5.1 UDP C/S编程架构

UDP C/S编程架构

5.2 UDP C/S端注意点

UDP 客户端(Client)注意点

1、本地 IP、本地端口(我是谁)
2、目的 IP、目的端口(发给谁)
3、在客户端的代码中,我们只设置了目的 IP、目的端口

​ 客户端的本地 ip、本地 port 是我们调用 sendto 的时候 linux 系统底层自动给客户端分配的;分配端口的方式为随机分配,即每次运行系统给的 port 不一样

UDP 服务端(Server)注意点

1、服务器之所以要 bind 是因为它的本地 port 需要是固定,而不是随机的
2、服务器也可以主动地给客户端发送数据
3、客户端也可以用 bind,这样客户端的本地端口就是固定的了,但一般不这样做

5.3. Socket常用函数接口及其原理

5.3.1 socket()

使用socket()函数创建套接字

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

功能:
创建一个用于网络通信的socket套接字(描述符)
参数:
family:协议族(AF_INET(IPv4)、AF_INET6(IPv6)、PF_PACKET(链路层编程)等 。
type:套接字类(SOCK_STREAM(流式套接字)、SOCK_DGRAM(数据报式套接字)、SOCK_RAW(原始套接字)等)。
protocol:协议类别(0、IPPROTO_TCP、IPPROTO_UDP等)。
返回值:
套接字
特点:
创建套接字时,系统不会分配端口
创建的套接字默认属性是主动的,即主动发起服务的请求;
当作为服务器时,往往需要修改为被动的
头文件:
#include <sys/socket.h>

IPv4及IPv6套接字地址结构

IPv4套接字地址结构 sockaddr_in

#include <netinet/in.h>
struct in_addr
{
    in_addr_t s_addr;//4字节
};
//IPv4地址结构
struct sockaddr_in
{
    sa_family_t sin_family;//2字节  协议
    in_port_t sin_port;//2字节   端口PORT
    struct in_addr sin_addr;//4字节  IP地址
    char sin_zero[8]//8字节 必须为0
};

IPv6套接字地址结构 sockaddr_in6 (非本文重点)

IPv4,IPv6通用地址结构:struct sockaddr

sockaddr

struct sockaddr {
    sa_family_t sa_family; // 2字节   
    char sa_data[14] //14字节
};

应用场景:

当调用编程接口函数,且该函数需要传入地址结构时需要用struct sockaddr进行强制转换

struct  sockaddr_in  my_addr; 
bind(sockfd,(sockaddr*)&my_addr,sizeof(my_addr)); 

5.3.2 bind()

 **让套接字 拥有一个固定的IP、端口信息**

bind只能绑定自身的IP

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

功能:
将本地协议地址与sockfd绑定
参数:
sockfd: socket套接字
myaddr: 指向特定协议的地址结构指针
addrlen:该地址结构的长度
返回值:
成功:返回0
失败:其他

5.3.3 sendto()(UDP)/ send()(TCP)

 **发送udp数据**
ssize_t sendto(int sockfd,const void *buf,size_t nbytes,int flags,const struct sockaddr *to, socklen_t addrlen);

功能:
向to结构体指针中指定的ip,发送UDP数据
参数:
sockfd:套接字
buf: 所要发送数据的内容。发送数据缓冲区
nbytes: 所要发送数据的内容大小。发送数据缓冲区的大小
flags:一般为0
to: 指向目的主机地址结构体的指针
addrlen:to所指向内容的长度
注意:
通过to和addrlen确定目的地址
可以发送0长度的UDP数据包
返回值:
成功:发送数据的字符数
失败: -1

5.3.4 recvfrom()(UDP)/recv()(TCP)

接受数据

ssize_t recvfrom(int sockfd, void *buf,size_t nbytes,int flags,struct sockaddr *from, socklen_t *addrlen);

功能:
接收UDP数据,并将源地址信息保存在from指向的结构中
参数:
sockfd: 套接字
buf: 接收数据缓冲区
nbytes: 接收数据缓冲区的大小
flags: 套接字标志(常为0)
from: 源地址结构体指针,用来保存数据的来源
addrlen: from所指内容的长度
注意:
通过from和addrlen参数存放数据来源信息
from和addrlen可以为NULL, 表示不保存数据来源
返回值:
成功:接收到的字符数
失败: -1

5.3.5 connect() (TCP)

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

5.3.6 listen() (TCP)

       int listen(int sockfd, int backlog);

5.3.7 accept() (TCP)

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

5.4 代码示例

5.4.1 proto.h

#ifndef PROTO_H__
#define PROTO_H__

#define RCVPORT    "1998"
#define NAMESIZE    1024

struct msg_st
{
    char name[NAMESIZE];
    uint32_t math;
    uint32_t chinese;
}__attribute__((packed));  //取消字节对齐


#endif 

5.4.2 client.cpp

socket – sendto – close

#include <iostream>
using namespace std;

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



#include "proto.h"



int main()
{
    int fd;
    // iPv4 结构体 sockaddr_in 
    struct sockaddr_in raddr;
    
    struct msg_st  rbuf;
    
    
    //创建一个udp套接字
    fd = socket(AF_INET,SOCK_DGRAM,0);
    
    if(fd<0)
    {
        perror("socket():");
        exit(1);
    }
    
    //协议
    raddr.sin_family = AF_INET;
    //将主机字节序转换成网络字节序
    raddr.sin_port = htons(atoi(RCVPORT));
    //将字符串转换成32位整形数据 赋值IP地址
    inet_pton(AF_INET,"0.0.0.0",&raddr.sin_addr);


    cout<<"输入您的内容:"<<endl;
    scanf("%s",rbuf.name);
    
    //发送数据
    if(sendto(fd,&rbuf,sizeof(rbuf),0,(sockaddr *)&raddr,sizeof(raddr) )<0 )
    {
        perror("sendto()");
        exit(1);
    }
    
    //关闭套接字
    close(fd);
    
    exit(0);

}

5.4.3 server.cpp

socket – bind – recvfrom – close

#include <iostream>
using namespace std;

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


#include "proto.h"

int main()
{
    int fd;
    struct sockaddr_in  laddr,raddr;
    struct msg_st  rbuf;
    socklen_t addrlen;
    

    fd = socket(AF_INET,SOCK_DGRAM,0);
    if(fd<0)
    {
        perror("socket():");
        exit(1);
    }
    
    raddr.sin_family = AF_INET;
    raddr.sin_port = htons(atoi(RCVPORT));
    inet_pton(AF_INET,"0.0.0.0",&raddr.sin_addr);
    
    //收取数据要bind
    if(bind(fd,(sockaddr *)&raddr,sizeof(raddr) ) <0)
    {
        perror("bind():");
        exit(1);
    }
    
    addrlen = sizeof(laddr);
    while(1)
    {
    
        if(recvfrom(fd,&rbuf,sizeof(rbuf),0,(sockaddr *)&laddr,(socklen_t *)&addrlen )<0 )
        {
            perror("sendto()");
            exit(1);
        }
        
        cout<<rbuf.name <<endl;
    }
    
    close(fd);
    
    exit(0);

}

6.基于TCP的socket编程

注意要点:

TCP连接过程中应用层接口的各个含义原理

TCP数据报结构

三次握手建立连接原理

四次挥手断开连接原理

6.1 TCP C/S编程架构

sockaddr

6.2 TCP C/S 一般流程

Client(客户端): socket() -> connect() -> send()/recv() -> close()

Server(服务端):socket() -> bind() -> listen() -> accept() -> recv()/send() -> close()

6.3 代码示例

6.3.1 client.cpp

#include <iostream>
using namespace std;
#include <cstring>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>

int main()
{

    //client -----  socket -> connect -> send -> close
    
    //1.socket
    int c_fd = socket(AF_INET,SOCK_STREAM,0);
    if(c_fd == -1)
        cout << "socket():" <<strerror(errno) <<endl;
        
    //2.connect 
    
    sockaddr_in c_addr;
    c_addr.sin_family = AF_INET;
    c_addr.sin_port = htons(1989);
    inet_pton(AF_INET,"127.0.0.1",&c_addr.sin_addr);
    cout <<"?"<<endl;
        int ret =connect(c_fd, (sockaddr *)&c_addr,sizeof(c_addr));
    if(ret == -1)
        cout<<"connect():" <<strerror(errno) <<endl;
    
    //3.send


​    

    while(true)
    {
        char buf[3] = "NO";
        
        if ( send(c_fd,buf,sizeof(buf),0) ==-1)
             cout<<"send():" <<strerror(errno) <<endl;
     
        cout << "send successed! " <<endl;
        sleep(2);
    }
    
    //4.close
    close(c_fd);
    return 0;

}

6.3.2 server.cpp

#include <iostream>
using namespace std;
#include <iostream>
using namespace std;
#include <sys/types.h>           
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>

int main()
{
    int cnt=0;
    //server  socket->bind->listen->accept->recv->close    

    //1.socket
    int listenfd = socket(AF_INET,SOCK_STREAM,0);
    if(listenfd == -1)
    {
        cout<< "socket():" <<strerror(errno) <<endl;
        return -1;
    }


     //2.bind
     sockaddr_in bindaddr;    
     bindaddr.sin_family = AF_INET;
     bindaddr.sin_port = htons(1989);
     inet_pton(AF_INET,"0.0.0.0",&bindaddr.sin_addr);
     
     if ( bind(listenfd,(sockaddr *)&bindaddr,sizeof(bindaddr)) == -1)
     {
         cout<< "bind():" <<strerror(errno) <<endl;
         return -1;
     }

    //3.listen
    if ( listen(listenfd,8)  == -1)
    {
        cout<< "listen():" <<strerror(errno) <<endl;
        return -1;
    }
    
    //4.accept
    sockaddr_in clientaddr;
    socklen_t  clientaddrlen = sizeof(clientaddr);
    int clientfd = accept(listenfd, (sockaddr *)&clientaddr,&clientaddrlen);
    if(clientfd ==-1)
    {
        cout<< "accept():" <<strerror(errno) <<endl;
        return -1;
    }
    
    while(true)
    {
        char recvbuf[3];
        
        if(recv(clientfd,recvbuf,sizeof(recvbuf),0) ==-1)
        {
            cout << "recv():" << strerror(errno) <<endl;
            return -1;
        }
            ++cnt;
            cout << cnt <<"--connection is suceessed ! "<<endl;

    }
    
    //close(clientfd);
    close(listenfd);
    return 0;

}

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 351134995@qq.com

×

喜欢就点赞,疼爱就打赏