Linux下socket编程

1.socket简介

  在计算机领域,socket 翻译为“套接字”,它是计算机之间进行通信的一种约定,也可以认为是一种技术。学习 socket,就是学习计算机之间的通信,并能够用编程语言开发出实用的程序。

2.预备知识:IP地址, 端口号, 网络字节序等

2.1 IP地址(IP Address)
  想要和计算机进行通信,必须要知道其确切的地址。IP 地址是最常用的确定计算机位置的方式。事实上,我们的计算机并不知道 IP 地址对应的地理位置,当要通信时,只是将 IP 地址封装到要发送的数据包中,交给路由器去处理。路由器有非常智能和高效的算法,它会找到目标计算机,并将数据包传递给它,完成一次单向通信。
(1)IP地址是在IP协议中,用来标识网络中不同主机的地址;
(2)对于IPv4,IP地址是一个4字节,uint32_t(无符号32位整数,数量不到43亿)的整数;
(3)在网络通信中的每条数据中都应该包含有目的ip地址和源ip地址。

2.2 端口(Port)
  有了 IP 地址,虽可以找到目标计算机,但仍然不能进行通信。因为一台计算机可以同时提供多种网络服务,例如Web服务、FTP服务等,仅有 IP 地址,计算机虽然可以正确接收到数据包,但却不知道要将数据包交给哪个网络程序来处理,所以通信失败。为了区分不同的网络程序,计算机会为每个网络程序分配一个独一无二的端口号(Port Number),例如,Web服务的端口号是 80,FTP 服务的端口号是 21。端口(Port)是一个虚拟的、逻辑上的概念。可以将端口理解为一道门,数据通过这道门流入流出,每道门有不同的编号,就是端口号。
(1)端口号标识了一台主机上进行通信的不同的应用程序,端口号+IP地址可以组成一个套接字,用来标识一个进程;
(2)端口号是一个2字节,uint16_t(0~65535)的整数;

(1)0~1023:知名端口号,是留着备用的,一般都是用于协议,例如HTTP、FTP、SSH
(2)1024~65535:是操作系统动态分配的端口号,客户端程序的端口号,就是由操作糸统从这个范围来分配的,在TCP与UDP的套接字通信中,客户端的端口号就是在此范围中

(3)每条网络中的数据都应该包含源端口和目的端口;
(4)一个端口号只能被一个进程占用;一个进程可以使用多个端口号

  • 区分“端口号(port number)”和“进程ID(processID、PID)”

  1.一个端口号只能绑定一个进程,但是一个进程可以绑定多个端口号。
  2.就好比我们打电话给移动客服,打的都是10086,但是我们现在打和等会打,接待的移动客服的工号是不一样的。

   pid不是端口号,是操作系统对运行中程序的编号,是系统分配给一个进程的唯一标识符。pid就是各进程的身份标识符,程序一运行系统就会自动分配给进程一个独一无二的pid。进程终止后,pid被系统收回,可能会被继续分配给新运行的程序。

2.3 协议(Protocol)
   协议就是网络通信的约定,通信的双方必须都遵守才能正常收发数据。协议有多种,例如 TCP、UDP、IP 等,通信的双方必须使用同一协议才能通信。协议是一种规范,由计算机组织制定,规定了很多细节,例如,如何建立连接,如何相互识别等。

协议仅仅是一种规范,必须由计算机软件来实现。例如 IP 协议规定了如何找到目标计算机,那么各个开发商在开发自己的软件时就必须遵守该协议,不能另起炉灶。

  所谓协议族(Protocol Family),就是一组协议(多个协议)的统称。最常用的是 TCP/IP 协议族,它包含了 TCP、IP、UDP、Telnet、FTP、SMTP 等上百个互为关联的协议,由于 TCP、IP 是两种常用的底层协议,所以把它们统称为 TCP/IP 协议族。

2.4 数据传输方式
  计算机之间有很多数据传输方式,各有优缺点,常用的有两种:SOCK_STREAM 和 SOCK_DGRAM。
(1) SOCK_STREAM 表示面向连接的数据传输方式。数据可以准确无误地到达另一台计算机,如果损坏或丢失,可以重新发送,但效率相对较慢。常见的 http 协议就使用 SOCK_STREAM 传输数据,因为要确保数据的正确性,否则网页不能正常解析。
(2) SOCK_DGRAM 表示无连接的数据传输方式。计算机只管传输数据,不作数据校验,如果数据在传输中损坏,或者没有到达另一台计算机,是没有办法补救的。也就是说,数据错了就错了,无法重传。因为SOCK_DGRAM 所做的校验工作少,所以效率比SOCK_STREAM 高。QQ 视频聊天和语音聊天就使用 SOCK_DGRAM 传输数据,因为首先要保证通信的效率,尽量减小延迟,而数据的正确性是次要的,即使丢失很小的一部分数据,视频和音频也可以正常解析,最多出现噪点或杂音,不会对通信质量有实质的影响。
  有可能多种协议使用同一种数据传输方式,所以在 socket 编程中,需要同时指明数据传输方式和协议。

2.5 网络字节序
  内存中的多字节数据相对于内存地址有大端和小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有大小端之分,同样的,网络数据流也有大小端之分。定义网络字节序的原因是让不同cpu架构的计算机进行网络通信时,字节序不会混淆。

  1. 发送主机通常将发生缓冲区中的数据按内存地址从低到高的顺序发出;
  2. 接收主机把从网络上接收到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
  3. 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址;
  4. TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节;
  5. 不管这台主机是大端机还是小端机,都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
  6. 如果当前发送主机是小端机,就需要先将数据转为大端字节序,否则忽略直接发送即可。

为使网络程序具有可移植性,使同样的代码在大端和小端计算机上编译后都能正常运行,可调用以下库函数做网络字节序和主机字节序的转换。

#include <arpa/inet.h>

uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
  1. h表示host,n表示network,l表示32位长整数,s表示16位短整数;
  2. 例如htonl表示将32位长整数从主机字节序转换为网络字节序,例如将ip地址转换后准备发送;
  3. 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
  4. 如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。

3 socket套接字及socket API基本用法

3.1 套接字概念

套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。

3.2 TCP socket常见API

  1. 创建套接字
int socket(int domain,int type,int protocol);
//domain:地址域 IPv4:AF_INET,IPv6:AF_INET6
//type:套接字类型  SOCK_STREAM流式套接字,SOCK_DGRAM数据报套接字
//proto:传输层协议类型 0-默认,TCP:IPPROTO_TCP或6,UDP:IPPRPTO_UDP或17
//返回值:套接字操作句柄--文件描述符  创建失败返回-1

(1).socket()打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符;
(2).应用程序可以像读写文件一样用read/write在网络上收发数据;

  1. 为套接字绑定地址信息
int bind(int sockfd, const struct sockaddr* addr,socklen_t addrlen);
//sockfd:创建套接字成功后返回的套接字操作句柄
//addr:地址信息
//addlen:地址长度
//返回值:成功返回0,失败返回-1

(1).服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接; 服务器需要调用bind绑定一个固定的网络地址和端口号;
(2).bind()的作用是将参数sockfd和addr绑定在一起, 使sockfd这个用于网络通讯的文件描述符监听addr所描述的地址和端口号;
(3).struct sockaddr*是一个通用指针类型,addr参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度;

  1. 监听
int listen(int sockfd,int backlog);
//成功返回0,失败返回-1

(1).listen()声明sockfd处于监听状态, 并且最多允许有backlog个客户端处于连接等待状态, 如果接收到更多的连接请求就忽略, 这里设置不会太大(一般是5)

  1. 接受连接
int accept(int sockfd,struct sockaddr* addr,socklen_t addrlen);
//addr是一个传出参数,accept()返回时传出客户端的地址和端口号;
//如果给addr参数传NULL,表示不关心客户端的地址;
//addrlen参数是一个传入传出参数(value-result argument), 传入的是调用者提供的缓冲区addr的长度以避免缓冲区溢出问题, 传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区);

(1).三次握手完成后, 服务器调用accept()接受连接;
(2).如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来;

  1. 连接请求
 int connect(int sockfd, const struct sockaddr* addr,socklen_t addrlen);
 //返回值:成功返回0,失败返回-1

(1).客户端需要调用connect()连接服务器;
(2).connect和bind的参数形式一致, 区别在于bind的参数是自己的地址, 而connect的参数是对方的地址;

  1. 关闭套接字
 int close(int sockfd);

3.3 sockaddr结构
socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6。但是,各种网络协议的地址格式并不相同。

sockaddr结构

struct sockaddr{
    sa_family sa_family;//指定当前地址信息是哪一个地址域,ipv4 or ipv6
    char sa_data[14];//补全位
};

sockaddr_in结构(虽然socket api的接口是sockaddr,但是在基于IPv4编程时,使用的数据结构是sockaddr_in,这个结构里主要有三部分信息:地址类型、端口号、ip地址)

struct sockaddr_in{//ipv4的地址信息结构
    _SOCKADDR_COMMOM(sin_);
    in_port_t sin_port;//port number
    struct in_addr sin_addr;//internet address
};

in_addr结构

//typedef uint32_t in_addr_t;
struct in_addr{
	in_addr_t s_addr;
};

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值