Linux网络编程和套接字

时间:2021-03-12 10:22:29

1、套接字概述

套接字的本意是插座,在网络中用来描述计算机中不同程序与其他计算机程序的通信方式。
常用的套接字类型有3种:
1)流套接字(SOCK——STREAM):使用了面向连接的可靠的数据通信方式,即TCP套接字
2)数据报套接字(Raw Sockets):使用了不面向连接的数据传输方式,即UDP套接字
3)原始套接字(SOCK——RAW):没有经过处理的IP数据包,可以根据自己程序的要求进行封装。

2、常用函数

1、创建套接字函数:成功时返回文件描述符,失败时返回-1

int socket(int domain,int type,int protocol);
//参数domain用于指定创建套接字所使用的协议族(可取AF_UNIX,AF_INET,AF_INTE6)
//参数type指定套接字的类型(可取SOCK_STREAM,SOCK_DGRAM,SOCK_RAW)
//参数protocol通常设置为0

2、在指定套接字上创建链接函数:成功时返回0,失败时返回-1

int connect(int sockfd,const struct sockaddr *serv_addr,socklen_t addrlen);
//参数sockfd是一个由函数socket创建的套接字
//参数serv_addr是一个地址结构,指定服务器的IP地址和端口号
//参数addrlen为参数serv_addr的长度

3、将一个套接字和某个端口绑定在一起的函数:成功时返回0,失败时返回-1

int bind(int sockfd,struct sockaddr *my_addr,socklen_t addrlen);
//一般只有服务器端的程序调用,参数my_addr指定了sockfd将绑定到的本地
//地址,可以将参数my_addr的sin_addr设置为INADDR_ANY而不是某个确定
//IP地址就可以绑定到任何网络接口。

4、把套接字转化为被动监听函数:成功时返回0,失败时返回-1

int listen(int s,int backlog);
//参数s为套接字,参数backlog指定链接请求队列的最大长度;

5、接收连接请求函数:成功时返回文件描述符,失败时返回-1

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

6、在TCP套接字上发送数据函数:有连接

包含3要素:套接字s,待发数据msg,数据长度len

ssize_t send(int s,const void *msg,size_t len,int flags);
//函数只能对处于连接状态的套接字使用,参数s为已建立好连接的套接字描述
//符,即accept函数的返回值
//参数msg指向存放待发送数据的缓冲区
//参数len为待发送数据的长度,参数flags为控制选项,一般设置为0

7、在TCP套接字上接收数据函数:有连接

包含3要素:套接字s,接收缓冲区buf,长度len

ssize_t recv(int s,void *buf,size_t len,int flags);
//函数recv从参数s所指定的套接字描述符(必须是面向连接的套接字)上接收
//数据并保存到参数buf所指定的缓冲区
//参数len则为缓冲区长度,参数flags为控制选项,一般设置为0

8、在UCP套接字上发送数据函数:无连接

ssize_t sendto(int s,const void *msg,size_t len,int flags,const struct sockaddr *to,socklen_t tolen);
//函数功能与函数send类似,但函数sendto不需要套接字处于连接状态,所以
//该函数通常用来发送UDP数据,同时因为是无连接的套接字,在使用sendto时
//需要指定数据的目的地址,参数msg指向待发送数据的缓冲区。
//参数len指定了待发送数据的长度
//参数flags是控制选项,含义与send函数中的一致
//参数to用于指定目的地址,目的地址的长度由tolen指定

9、在UDP套接字上接收数据函数:无连接

ssize_t recvfrom(int s ,void *buf,size_t len,int flags,struct sockaddr *from,socklen_t *fromlen);
//与函数recv功能类似,只是函数recv只能用于面向连接的套接字,而函数
//recvfrom没有此限制,可以用于从无连接的套接字上接收数据
//参数buf指向接收缓冲区
//参数len指定了缓冲区的大小
//参数flags是控制选项,含义与recv中的一致
//如果参数from非空,且该套接字不是面向连接的,则函数recvfrom返回时,
//参数from中将保存数据的源地址
//参数fromlen在调用recvfrom前为参数from的长度,调用recvfrom后将
//保存from的实际大小

10、关闭套接字函数:

int close(int fd);
//参数fd为一个套接字描述符;

11、多路复用函数:

int select(int n,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);
//参数n是需要监视的文件描述符数
//参数readfds指定需要监视的可读文件描述符集合
//参数writefds指定需要监视的可写文件描述符集合
//参数exceptfds指定需要监视的异常文件描述符的集合
//参数timeout指定了阻塞的时间

3、服务器端套接字(接收连接请求的套接字)创建过程

第一步:调用socket函数创建套接字。
第二步:调用bind函数分配IP地址和端口号。
第三部:调用listen函数转为可接收请求状态。
第四步:调用accept函数受理连接请求。

另外还有read/write,以及close。

4、客户端套接字(发送连接请求的套接字)创建过程

只有两步:
1、调用socket函数创建套接字。
2、调用connect函数向服务器端发送连接请求。

另外还有read/write,以及close。

5、Linux的文件操作

文件描述符:是系统自动分配给文件或套接字的整数。

每当生成文件或套接字,操作系统就会自动返回给我们一个整数。这个整数就是文件描述符,即创建的文件或套接字的别名,方便称呼而已。文件描述符在Windows中又称为句柄。

1、打开文件:
Linux网络编程和套接字

2、关闭文件或套接字:
Linux网络编程和套接字

3、将数据写入文件:
Linux网络编程和套接字

4、读取文件中的数据:
Linux网络编程和套接字

注:ssize_t = signed int, size_t = unsigned int,都是通过typedef声明,为基本数据类型取的别名。既然已经有了基本数据类型,那么为什么还需要为它取别名呢?是因为目前普遍认为int是32位的,而过去16位操作系统时代,int是16位的。根据系统的不同,时代的变化,基本数据类型的表现形式也随着变化的如果为基本数据类型取了别名,以后要修改,也就只需要修改typedef声明即可,这将大大减少代码变动。

6、服务器端实例

服务器端受理连接请求的程序。编译并运行该程序,创建等待连接请求的服务器端。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
void error_handling(char *message);

int main(int argc, const char * argv[])
{
int serv_sock;
int clnt_sock;

struct sockaddr_in serv_addr;
struct sockaddr_in clnt_addr;
socklen_t clnt_addr_size;

char message[] = "Hello World!";

if(argc != 2)
{
printf("Usage:%s <port>\n", argv[0]);
exit(1);
}
//(1)调用socket函数创建套接字
serv_sock = socket(PF_INET, SOCK_STREAM, 0);
if(serv_sock == -1)
error_handling("socket() error");

memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(atoi(argv[1]));
//(2)调用bind函数分配IP地址和端口号
if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)
error_handling("bind() error");
//(3)调用listen函数转为可接收请求状态
if(listen(serv_sock, 5) == -1)
error_handling("listen() error");

clnt_addr_size = sizeof(clnt_addr);
//(4)调用accept函数受理连接请求.没有连接请求时调用不会返回,直到有请求
clnt_sock = accept(serv_sock, (struct sockaddr*) &clnt_addr, &clnt_addr_size);
if(clnt_sock == -1)
error_handling("accept() error");
//(5)write函数用于传输数据。执行到此行说明已有了连接请求
//向文件描述符为clnt_sock的客户端文件中传输message中的数据
write(clnt_sock, message, sizeof(message));
close(clnt_sock);//(6)
close(serv_sock);//(7)

return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}

7、客户端实例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
void error_handling(char *message);

int main(int argc, const char * argv[])
{
int sock;
struct sockaddr_in serv_addr;
char message[30];
int str_len;

if(argc != 3)
{
printf("Usage: %s <IP> <port>\n", argv[0]);
exit(1);
}
//(1)创建套接字.但此时并不马上分为客户端或服务器端
sock = socket(PF_INET, SOCK_STREAM, 0);
if(sock == -1)
error_handling("socket() error");

memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
serv_addr.sin_port = htons(atoi(argv[2]));
//(2)调用connect函数向服务器端发送连接请求.此时确定为客户端
if(connect(sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1)
error_handling("connect() error");
//(3)调用read函数向自身声明的message数组中保存数据
str_len = read(sock, message, sizeof(message) - 1);
if(str_len == -1)
error_handling("read() error");

printf("Message from server: %s \n", message);
close(sock);//(4)

return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}

7、基本客户/服务器套接字图形展示

Linux网络编程和套接字

参考:《TCP/IP网络编程》及《Unix网络编程卷1》