一、简介
在Linux系统中,网络编程是通过socket接口来实现的。socket是一种特殊的I/O接口,也是文件描述符。socket是进程间通信的常用机制,特别的,可以通过网络实现不同主机之间的通信。
每一个 socket 都用一个半相关描述{协议、本地地址、本地端口}来表示;一个完整的套接字则用一个相关描述{协议、本地地址、本地端口、远程地址、远程端口}来表示。 socket 也有一个类似于打开文件的函数调用,该函数返回一个整型的 socket 描述符,随后的连接建立、数据传输等操作都是通过 socket 来实现的。
常用的socket类型:
/* Types of sockets. */ enum __socket_type { SOCK_STREAM = 1, /* Sequenced, reliable, connection-based byte streams. */ #define SOCK_STREAM SOCK_STREAM SOCK_DGRAM = 2, /* Connectionless, unreliable datagrams of fixed maximum length. */ #define SOCK_DGRAM SOCK_DGRAM SOCK_RAW = 3, /* Raw protocol interface. */ #define SOCK_RAW SOCK_RAW SOCK_RDM = 4, /* Reliably-delivered messages. */ #define SOCK_RDM SOCK_RDM SOCK_SEQPACKET = 5, /* Sequenced, reliable, connection-based, datagrams of fixed maximum length. */ #define SOCK_SEQPACKET SOCK_SEQPACKET SOCK_DCCP = 6, /* Datagram Congestion Control Protocol. */ #define SOCK_DCCP SOCK_DCCP SOCK_PACKET = 10, /* Linux specific way of getting packets at the dev level. For writing rarp and other similar things on the user level. */ #define SOCK_PACKET SOCK_PACKET /* Flags to be ORed into the type parameter of socket and socketpair and used for the flags parameter of paccept. */ SOCK_CLOEXEC = 02000000, /* Atomically set close-on-exec flag for the new descriptor(s). */ #define SOCK_CLOEXEC SOCK_CLOEXEC SOCK_NONBLOCK = 00004000 /* Atomically mark descriptor(s) as non-blocking. */ #define SOCK_NONBLOCK SOCK_NONBLOCK };
①SOCK_STREAM
流式套接字提供可靠的、面向连接的通信流;它使用 TCP 协议,从而保证了数据传输的正确性和顺序性。
②SOCK_DGRAM
数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证是可靠、无差错的。它使用数据报协议 UDP。
③SOCK_RAW
原始套接字允许对底层协议如 IP 或 ICMP 进行直接访问,它功能强大但使用较为不便,主要用于一些协议的开发。
二、重要接口及数据结构介绍
A、数据结构
sockaddr 和 sockaddr_in,这两个结构类型都是用来保存 socket 信息的,这两个数据类型是等效的,可以相互转化,通常 sockaddr_in 数据类型使用更为方便。在建立 socketadd 或 sockaddr_in 后,就可以对该 socket 进行适当的操作了。如下所示:

sa_family 字段可选的常见值:
B、主要API
int socket(int domain, int type, int protocol);
该函数用于建立一个 socket 连接,可指定 socket 类型等信息。在建立了 socket 连接之后,可对 sockaddr 或 sockaddr_in 结构进行初始化,以保存所建立的 socket 地址信息。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
该函数是用于将本地 IP 地址绑定到端口号,若绑定其他 IP 地址则不能成功。另外,它主要用于 TCP 的连接,而在 UDP 的连接中则无必要。
int listen(int sockfd, int backlog);
在服务端程序成功建立套接字和与地址进行绑定之后,还需要准备在该套接字上接收新的连接请求。此时调用 listen()函数来创建一个等待队列,在其中存放未处理的客户端连接请求。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
服务端程序调用 listen()函数创建等待队列之后,调用 accept()函数等待并接收客户端的连接请求。它通常从由 bind()所创建的等待队列中取出第一个未处理的连接请求。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
该函数在 TCP 中是用于 bind()的之后的 client 端,用于与服务器端建立连接,而在 UDP 中由于没有了 bind()函数,因此用 connect()有点类似 bind()函数的作用。
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
这两个函数分别用于发送和接收数据,可以用在 TCP 中,也可以用在 UDP 中。当用在 UDP 时,可以在 connect()函数建立连接之后再用。
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
这两个函数的作用与 send()和 recv()函数类似,也可以用在 TCP 和 UDP 中。当用在 TCP 时,后面的几个与地址有关参数不起作用,函数作用等同于 send()和 recv();当用在 UDP 时,可以用在之前没有使用connect()的情况下,这两个函数可以自动寻找指定地址并进行连接。
下面来看下socket接口调用流程,下图为使用TCP协议时:

下图为使用UDP协议通信时:

三、实例操作
通过编写下面实例来学习巩固,本实例通过SOCK_STREAM类型通信。代码分为客户端和服务器端两部分,其中服务器端首先建立起 socket,然后与本地端口进行绑定,接着就开始接收从客户端的连接请求并建立与它的连接,接下来,接收客户端发送的消息。客户端则在建立 socket 之后调用 connect()函数来建立连接,之后发送消息到服务端。
/* * FILE: server.c * NOTE: socket网络编程学习,server端程序 * * TIME: 2021年11月12日23:32:53 */ #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #define PORT 1234 #define BUFFER_SIZE 1024 #define MAX_QUE_CONN_NM 5 int main(int argc, char *argv[]) { struct sockaddr_in server_sockaddr, client_sockaddr; int sin_size, recvBytes; int sockfd, client_fd; char buf[BUFFER_SIZE]; /* 创建socket */ if ((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1) { perror("socket"); return -1; } printf("socket id = %d\n", sockfd); /* 设置sockaddr_in 结构体参数 */ server_sockaddr.sin_family = AF_INET; //IPv4 server_sockaddr.sin_port = htons(PORT); //端口号 server_sockaddr.sin_addr.s_addr = INADDR_ANY; bzero(&(server_sockaddr.sin_zero), 8); /* 允许重复使用本地地址与套接字进行绑定 */ int i = 1; setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)); /* 绑定到端口号(TCP使用) */ if (bind(sockfd, (struct sockaddr *)&server_sockaddr, sizeof(struct sockaddr)) == -1) { perror("bind"); return -1; } printf("bind success!\n"); /*调用 listen()函数,创建未处理请求的队列*/ if (listen(sockfd, MAX_QUE_CONN_NM) == -1) { perror("listen"); return -1; } printf("Listening...\n"); /*调用accept(), 阻塞等待客户端的连接*/ client_fd = accept(sockfd, (struct sockaddr *)&client_sockaddr, &sin_size); if (client_fd == -1) { perror("accept"); return -1; } /*调用recv(), 阻塞接收客户端的请求*/ memset(buf, 0, sizeof(buf)); recvBytes = recv(client_fd, buf, BUFFER_SIZE, 0); if (recvBytes == -1) { perror("recv"); return -1; } printf("Recv a message:%s \n", buf); /* 关闭socket */ close(sockfd); return 0; }
/* * FILE: client.c * NOTE: socket网络编程学习,Client端程序 * * TIME: 2021年11月12日23:32:53 */ #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <errno.h> #include <netdb.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #define PORT 1234 #define BUFFER_SIZE 1024 #define MAX_QUE_CONN_NM 5 int main(int argc, char *argv[]) { struct sockaddr_in serv_addr; int sendBytes; int sockfd, client_fd; char buf[BUFFER_SIZE]; struct hostent *host; /* 地址解析函数 */ host = gethostbyname(argv[1]); if (host == NULL) { perror("gethostbyname"); return -1; } memset(buf, 0, sizeof(buf)); sprintf(buf, "%s", argv[2]); /* 创建socket */ if ((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1) { perror("socket"); return -1; } printf("socket id = %d\n", sockfd); /* 设置sockaddr_in 结构体参数 */ serv_addr.sin_family = AF_INET; //IPv4 serv_addr.sin_port = htons(PORT); //端口号 serv_addr.sin_addr = *((struct in_addr *)host->h_addr); bzero(&(serv_addr.sin_zero), 8); /* 调用 connect 函数主动发起对服务器端的连接 */ if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr)) == -1) { perror("connect"); return -1; } /* 发送消息给服务器端 */ sendBytes = send(sockfd, buf, strlen(buf), 0); if (sendBytes == -1) { perror("send"); return -1; } printf("Send a message:%s \n", buf); /* 关闭socket */ close(sockfd); return 0; }
编译:gcc client.c -o client;
gcc server.c -o server
运行测试,先运行./server,然后再运行./client localhost 'hello world!',server成功收到client发送的字符串。


浙公网安备 33010602011771号