Linux下C语言Socket编程

时间:2022-05-15 05:19:24

Linux下C语言Socket编程

啥是Socket

socket用中国话将叫做”套接字”,是用来进行网络数据传输的一种约定或者说是一种.

我们通常说的TCP协议和UDP协议都是通过socket来进行连接.

一些预备知识点

IP地址

每一台连接到互联网的设备都会有一个IP地址,IP地址就像一个电话号码或者说家庭住址,通过IP才可以唯一地定位到我们要进行网络通信的网络设备

端口

一个设备一般只有一个IP地址,但是却可能同时运行着多个进行网络数据交流的应用程序,如果只有IP地址,计算机虽然可以正确接收到数据包,但是却不知道要将数据包交给哪个网络程序来处理.

为了区分不同的网络程序,计算机会为每个网络程序分配一个独一无二的端口号(Port Number),例如,Web服务的端口号是 80,FTP 服务的端口号是 21,SMTP 服务的端口号是 25。

端口(Port)是一个虚拟的、逻辑上的概念。可以将端口理解为一道门,数据通过这道门流入流出,每道门有不同的编号,就是端口号。

数据传输方式

计算机之间有很多数据传输方式,各有优缺点,常用的有两种:SOCK_STREAM 和 SOCK_DGRAM。

1) SOCK_STREAM 表示面向连接的数据传输方式。数据可以准确无误地到达另一台计算机,如果损坏或丢失,可以重新发送,但效率相对较慢。常见的 http 协议就使用 SOCK_STREAM 传输数据,因为要确保数据的正确性,否则网页不能正常解析。

2) SOCK_DGRAM
表示无连接的数据传输方式。计算机只管传输数据,不作数据校验,如果数据在传输中损坏,或者没有到达另一台计算机,是没有办法补救的。也就是说,数据错了就错了,无法重传。因为
SOCK_DGRAM 所做的校验工作少,所以效率比 SOCK_STREAM 高。

QQ 视频聊天和语音聊天就使用 SOCK_DGRAM 传输数据,因为首先要保证通信的效率,尽量减小延迟,而数据的正确性是次要的,即使丢失很小的一部分数据,视频和音频也可以正常解析,最多出现噪点或杂音,不会对通信质量有实质的影响。

​ 注意:SOCK_DGRAM 没有想象中的糟糕,不会频繁的丢失数据,数据错误只是小概率事件。

有可能多种协议使用同一种数据传输方式,所以在 socket 编程中,需要同时指明数据传输方式和协议。

使用socket()建立socket

在linux下,一切皆文件,socket也不例外,我们可以用操作文件的I/O函数来操作一个”socket文件”,但是,怎么建立一个socket文件呐?这需要用到一个在 < sys/socket.h >中的函数—-socket().

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

  1. af 为地址族(Address Family),也就是 IP 地址类型,常用的有 AF_INET 和 AF_INET6。AF 是“Address Family”的简写,INET是“Inetnet”的简写。AF_INET 表示 IPv4 地址,例如 127.0.0.1;AF_INET6 表示 IPv6 地址,例如 1030::C9B4:FF12:48AA:1A2B。
  2. type 为数据传输方式,常用的有 SOCK_STREAM 和 SOCK_DGRAM,二者区别上面说过.
  3. protocol 表示传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议。

常用:

TCP套接字:
int tcp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //IPPROTO_TCP表示TCP协议
UDP套接字:
int udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); //IPPROTO_UDP表示UDP协议

使用bind()和connect()定位socket

虽然用socket()建立了一个”socket文件”,但是这个socket文件还不知道要与哪个IP的哪个端口(port)进行通信呐~所以我们需要用bind()和connect()来为socket进行配置.

消息通话的发起段一般使用connect()函数来像另一端发送请求连接,而等待接受连接的一端使用bind来定义socket要接受哪个端口传来的连接请求.

connect()

connect()的原型如下:

int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen); //Linux

其中:

1. int sock 为我们定义的socket文件
2.struct sockaddr *serv_addr 为sockaddr结构体变量的指针

因为sockaddr结构体的赋值比较麻烦,要把转化好的ip与端口结合起来写入,所以一般使用sockaddr_in,然后通过强制类型转化来带入connect().

    struct sockaddr_in{
sa_family_t sin_family; //地址族(Address Family),也就是地址类型
uint16_t sin_port; //16位的端口号
struct in_addr sin_addr; //32位IP地址
char sin_zero[8]; //不使用,一般用0填充
};
  • sockaddr_in结构体的第一个变量sin_family表示地址类型,其取值与前面说的socket()的第一个参数的取值是相同的.

  • 第二个变量sin_port是让我们指定要发出信息的端口号,理论上其取值范围为0~65535,不过系统已经把0~1023分配给了特定的服务,所以一般我们自己的程序都会指定1024~65535之间的端口号.

    sin_port要求必须要采用网络数据格式,普通数字可以用htons()函数转换成网络数据格式的数字

  • 第三个变量类型为in_addr,只是一个用于储存ip地址的变量

      struct in_addr{
    in_addr_t s_addr; //32位的IP地址
    };

    而在这个结构体里的in_addr_t类型与unsigned long一样的….不过我们输入ip的时候因为有四个点,所以肯定要用char数组啊,so,我们需要一个函数把一个字符串转化为in_addr_t的值,正好,我们有这个函数,叫做—–inet_addr().

    so,我们可以这样写

    in_addr_t a; 
    a=inet_addr("127.0.0.1");
  • 最后的sin_zero[8]是多余的,是为了让sockaddr_in的结构体大小与sockaddr相同.

3.socklen_t addrlen 为addr变量的大小

一般直接写sizeof(struct sockaddr)

0.0

bind()

bind()的原型如下:

int bind(int sock, struct sockaddr *serv_addr, socklen_t addrlen); //Linux

可以看到,bind()的参数与connnect是一模一样的,其实他们的功能也差不多.都是配置socket属性,只不过使用bind定义的socke文件将会用来被动地等待连接,而connect()定义的socket文件讲会主动出击,去连接其他设备.

因为差不多,参考上一条吧~~~ /笑cry

不过要注意的一点是,使用connect就立即向目的IP发送连接请求,而使用bind()函数进行IP与端口绑定后,还要使用listen()函数使socket所指向的端口处于监听状态,使用accept()接受请求.

bind()后的listen()与accept()


未完待续