Linux网络编程--套接字编程

时间:2022-12-15 08:16:16

一、套接字

1、套接字地址结构

1.struct sockaddr

结构struct sockaddr定义了一种通用的套接字地址,它在linux/socket.h中的定义代码如下:

struct sockaddr {
unsigned short sa_family; //地址类型
char sa_data[14]; //14字节的协议地址
};

sa_family:套接字的协议族类型
sa_data:存储具体的协议地址
一般在编程中并不对该结构体进行操作,而是使用另一个与它等价的数据结构:sockaddr_in

2.struct sockaddr_in

每种协议族都有自己的协议地址格式,TCP/IP协议族的地址格式为结构体struct sockaddr_in,它在netinet/in.h头文件中定义,格式如下:

struct sockaddr_in {
unsigned short sin_family; //地址类型
unsigned short int sin_port; //端口号
struct in_addr sin_addr; //IP地址
unsigned char sin_zreo[8]; //填充字节,一般赋值为0
};

sin_family:表示地址类型,对于使用TCP/IP协议进行的网络编程,该值只能是AF_INET
sin_addr:用来存储32位的IP地址

struct in_addr的定义如下:

struct in_addr {
unsigned long s_addr;
};

结构体sockaddr的长度为16字节,结构体sockaddr_in的长度也为16字节。通常在编写基于TCP/IP协议的网络程序时,使用结构体sockaddr_in来设置地址,然后通过强制类型转换成sockaddr类型

2、创建套接字

socket函数

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

domain:指定创建套接字所使用的协议族,它们在头文件linux/socket.h中定义。常用的协议族如下:

  • AF_UNIX:创建只在本机内进行通信的套接字
  • AF_INET:使用IPv4TCP/IP协议
  • 使用IPv6TCP/IP协议

type:指定套接字的类型

  • SOCK_STREAM:创建TCP流套接字
  • SOCK_DGRAM:创建UDP数据报套接字
  • SOCK_RAW:创建原始套接字

procotol:通常设置为0,表示通过参数domain指定的协议族和参数type指定的套接字类型来确定使用的协议。当创建原始套接字时,系统无法惟一地确定协议,此时就需要使用该参数指定所使用的协议。

执行成功返回一个新创建的套接字;若有错误发生则返回-1,错误代码存入errno中。

3、建立连接

connect函数

#include<sys/types.h>
#include<sys/socket.h>
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);

sockfd:是一个由socket创建的套接字。如果该套接字类型是SOCK_STREAM,则connect函数向服务器发出连接请求。如果套接字的类型是SOCK_DGRAM,则connect函数并不建立真正的连接,它只是告诉内核与该套接字进行通信的目的地值,只有该目的地址发来的数据才会被该socket接受。

serve_addr:服务器的IP地址和端口号。

  • 通常一个面向连接的套接字(如TCP套接字)只能调用一次connect函数。而对于无连接的套接字(如UDP套接字)则可多次调用connect函数以改变与目的地址的绑定。将参数serv_addr中的sa_family设置为AF_UNSPEC可以取消绑定。

addrlen:参数serv_addr的长度。

执行成功返回0,有错误发生则返回-1,错误代码存入errno中。

4、绑定套接字

bind函数

#include<sys/types.h>
#include<sys/socket.h>
int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);

sockfd:是一个由socket创建的套接字。
my_addr:制定了sockfd将绑定到的本地地址,可以将my_addr的sin_addr设置为INADDR_ANY而不是某个确定的IP地址就可以绑定到任何网络接口。

函数执行成功返回0,当有错误发生时返回-1。

5、在套接字上监听

listen函数

由函数socket创建的套接字是主动套接字,这种套接字可以用来主动请求连接到某个服务器(通过函数connect)。但是作为服务器端的程序,通常在某个端口上监听等待来自客户端的连接请求。

#include<sys/socket.h>
int listen(int s, int backlog);

backlog:指定了该连接队列的最大长度。如果连接队列已经达到最大,之后的连接请求将被服务器拒绝。

执行成功返回0,当有错误发生返回-1。

6、接受连接

accept函数

只能对面向连接的套接字使用accept函数,accept执行成功时,将创建一个新的套接字,并且为这个新的套接字分配一个套接字描述符,并返回这个新的套接字的描述符。这个新的套接字描述符与打开文件时返回的文件描述符类似,进程可以利用这个新的套接字描述符与客户端交换数据,参数s所指定的套接字继续等待客户端的连接请求。

#include<sys/types.h>
#include<sys/socket.h>
int accept(int s, struct sockaddr *addr, socklen_t *addrlen);

s:由函数socket创建,经函数bind绑定到本地某一端口上,然后通过函数listen转化而来的监听的套接字。
addr:用来保存发起连接请求的主机的地址和端口。
addrlen:addr所指向的结构体的大小。

执行成功返回一个新的代表客户端的套接字,出错返回-1。

7、关闭套接字

1、close函数

关闭一个套接字

#include<unistd.h>
int close(int fd);

fd :一个套接字描述符

执行成功返回0,出错返回-1。

2、shutdown函数

shutdown函数与close函数类似,但是shutdown函数功能更强大。它允许对套接字进行单向关闭或全部禁止。

#include<sys/socket.h>
int shutdown(int s, int how);

s:待关闭的套接字描述符
how:指定了关闭方式,具体取值如下

  • SHUT_RD:将连接上的读通道关闭,此后进程将不能再接收任何数据,接收缓冲区中还未被读取的数据也将被丢弃,但仍可以在该套接字上发送数据
  • SHUT_WR:将连接上的写通道关闭,此后进程将不能再发送任何数据,发送缓冲区中还未被发送的数据也将被丢弃,但仍可以在该套接字上接收数据
  • SHUT_RDWD:读写通道都将被关闭

二、TCP套接字的数据传输

1、发送数据

send函数

函数send只能对于处于连接状态的套接字使用。如果要发送的数据长度大于该套接字的缓冲区剩余空间大小时,send()一般会被阻塞,如果该套接字被设置为非阻塞方式,则此时立即返回-1并将errno设为EAGAIN.

#include<sys/types.h>
#include<sys/socket.h>
ssize_t send(int s, const void *msg, size_t len, int flags);

s:已建立好连接的套接字描述符

msg:指向存放待发送数据的缓冲区

len:待发送数据的长度
flags:控制选项

  • MSG_OOB:在指定的套接字上发送带外数据
  • MSG_DONTROUTE:通过最直接的路径发送数据,而忽略下层协议的路由设置

执行成功返回实际发送数据的字节数,出错则返回-1。

2、接受数据

recv函数

如果一个数据包太长以至于缓冲不能完全放下时,剩余部分的数据将可能被丢弃(根据接受数据的套接字类型而定)。如果在指定的套接字上无数据到达时,recv()将被阻塞,如果该套接字被设置为非阻塞方式,则立即返回-1。函数recv接收到数据就返回,并不会等待接收到参数len指定长度的数据才返回。

#include<sys/types.h>
#include<sys/socket.h>
ssize_t recv(int s, void *buf, size_t len, int flags);

s:套接字描述符

buf:指定的缓冲区

len:缓冲区长度

flags:控制选项一般设置为0或取以下数值

  • MSG_OOB:请求接受带外数据
  • MSG_PEEK:只查看数据而不读出
  • MSG_WAITALL:只在接收缓冲区满时才返回

执行成功返回收到的数据字节数,出错则返回-1。

三、UDP套接字的数据传输

1、发送数据

sendto函数

sendto的功能与send类似,但函数sendto不需要套接字处于连接状态,所以该函数通常用来发送UDP数据,并且需要指定数据的目的地址

#include<sys/types.h>
#include<sys/socket.h>
ssize_t sendto(int s, const void *msg, size_t len, int flags, const struct sockaddr *to, sockle_t tolen);

to:目的地址

tolen:目的地址的长度

执行成功返回实际发送数据的字节数,出错则返回-1。

2、接受数据

recvfrom函数

recvform函数和recv函数功能类似,只是函数recv只能用于面向连接的套接字,而函数recvform没有此限制,可以用于从无连接的套接字(如UDP套接字)上接收数据。

#include<sys/types.h>
#include<sys/socket.h>
ssize_t recvfrom(int s,void *buf,size_t len,int flags, struct sockaddr *from, socklen_t *fromlen)

buf:指向接收缓冲区

len:缓冲区的大小

flags:控制选项,与recv一致

from:如果参数from非空,且该套接字不是面向连接的,则函数recvfrom返回时,参数from中将保存数据的源地址

fromlen:参数fromlen在调用recvfrom前为参数from的长度,调用recvfrom后将保存from的实际大小。

执行成功返回实际接收到数据的字节数,出错则返回-1。