1.Socket简介
前面学习了管道、FIFO、共享内存、消息队列、信号量等这些IPC方法可以在进程之间进行通信,但它们都仅仅限于本机进程之间进行通信。那么不同主机间的进程就可以通过socket使用网络让不同主机上的应用程序进行通信。我们知道互联网是通过TCP/IP协议完成通信。而SOCKET把TCP/IP复杂的协议族集成为相关函数,通过调用socket相关函数就可以完成网络通信。
socket分类
socket分为流socket和数据报socket:
-
流socket提供了一个可靠的双向的字节流通信信道,即保证发送者传输的数据会完整地到达接收程序,流socket的正常工作需要一对相互连接的socket。
-
数据报socket允许数据以消息的形式进行交换,在数据报socket中,消息边界得到了保留,但数据传输是不可靠的。消息的到达可能是无序的、重复的或根本无法到达。数据报socket在使用时无需与另一个socket连接。
-
流socket使用了传输控制协议(TCP),数据报socket使用了用户数据报协议(UDP)。
2.流socket(TCP)
流socket通常分为主动socket和被动socket,被动socket通常也叫服务器,主动socket通常叫服务端。
服务器端socket
-
通过上图我们可以看到服务器端的流程是:
-
调用socket()创建一个新的socket。
-
调用bind()将创建的socket绑定在一个地址上,即分配ip和端口。
-
调用listen()监听客户端访问。
-
调用accept()接收客户端的访问
-
调用read()/write()或者send()/recv()传输数据。
-
注意事项:
①为了便于使用bind()函数,有必要了解一下socket的通用的地址结构struct sockaddr,如下所示。bind()使用时可能有不同结构的socket地址,所以定义了如下的标准地址结构,每种具体的socket地址是在标准地址下的再一次细分,如IPv4 socket地址会被存储在socket_in结构中,如下定义。所以在使用时需要用(struct sockaddr *)转化为标准格式。
struct sockaddr{
sa_family_t sa_family;
char sa_data[14];
}
struct sockaddr_in{ //IPv4 socket address
sa_family_t sin_family; //Address family(AF_INET)
in_port_t sin_port; //Port number
struct in_addr sin_addr; //IPv4 addreddr{ //IPv4 4-byte address
in_addr_t s_addr; //unsigned 32-bit integer
};
②设置端口号,计算机和网络传输时可能大小端模式不一样,网络数据是以大端模式传输的,所以在设置端口号时应该都统一为网络字节序模式,即大端模式。用htons函数进行实现主机和网络字节序转换。
③计算机以二进制来表示端口和IP地址,但是通常我们现实中用点分式十进制表示ip地址,所以可以用inet_aton()和 inet_ntoa()函数进行相互转化。
下面创建了一个服务器函数server.c,自定义了一个端口,服务器通过while(1)一直等待客户端的访问,待有客户端访问时,接收其访问读取其ip地址并打印,并向客户端发送字符串信息,完成后关闭客户端的socket,等待下一个客户端访问。
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#define PORT_ID 6666
#define SIZE 100
int main(void)
{
int sockfd, client_sockfd;
struct sockaddr_in my_addr, client_addr;
int addr_len;
char welcome[SIZE] = "Welcome to connect to the sever!";
/*
struct sockaddr{
sa_family_t sa_family;
char sa_data[14];
}
struct sockaddr_in{ //IPv4 socket address
sa_family_t sin_family; //Address family(AF_INET)
in_port_t sin_port; //Port number
struct in_addr sin_addr; //IPv4 address
unsigned char _pad[x] //pad to size of 'sockaddr' structure(16 bytes)
};
*/
//初始化服务器的地址
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(PORT_ID);
my_addr.sin_addr.s_addr = INADDR_ANY; //可以接收任意客户端访问
//创建socket
sockfd = socket(AF_INET, SOCK_STREAM, 0);//create a socket
//AF_INET:IPv4; SOCK_STREAM:byte stream
//绑定socket
bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));
//监听客户端访问,队列最大为10
listen(sockfd, 10);
//10 : listening queue length is 10
addr_len = sizeof(struct sockaddr);
while(1)
{
printf("Server is waiting for client to connect:\n");
//允许并接收客户端的访问
client_sockfd = accept(sockfd, (struct sockaddr *)&client_addr, &addr_len);
printf("Client IP address = %s\n", inet_ntoa(client_addr.sin_addr));
send(client_sockfd, welcome, SIZE, 0);//发送数据
printf("Disconnect the client request.\n");
close(client_sockfd);
}
close(sockfd);
return 0;
}
客户端socket
客户端的流程是:
-
调用socket()创建一个新的socket
-
调用connect()将客户端通过地址请求连接到服务器的socket上。
-
调用read()/write()或者send()/recv()传输数据。
下面创建了一个客户端的程序client.c,主函数设置输入服务器的ip地址,完成请求通信后,接收服务器发来的字符串并打印。
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdlib.h>
#define PORT_ID 6666
#define SIZE 100
int main(int argc, char *argv[])
{
int sockfd, client_sockfd;
struct sockaddr_in server_addr;
char buf[SIZE];
if(argc < 2)
{
printf("Usge:./client [server IP address]\n");
exit(1);
}
sockfd = socket(AF_INET, SOCK_STREAM, 0);//create a socket
//AF_INET:IPv4; SOCK_STREAM:byte stream
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT_ID);
server_addr.sin_addr.s_addr =inet_addr((argv[1]));
connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr));
recv(sockfd, buf, SIZE, 0);
printf("Client receive from server: %s\n", buf);
close(sockfd);
return 0;
}
运行结果,由于只有一台电脑,所以服务器和客户端的ip地址是一样的,输入ifconfig,得到ip地址,一个终端运行server.c,此时服务器阻塞,打印"waitting...."后一直等待客户端的访问,另一个终端运行client.c并输入服务器ip地址,即可完成通信,服务器打印客户端的ip并发送字符串,客户端顺利收到并打印。
3.数据报socket(UDP)
数据报socket的流程如上图所示,相比流socket,数据报socket相对简单,因为它不用实时连接,运行类似于邮政系统。
服务器端socket
服务器端的流程是:
-
调用socket()创建一个socket,类似于创建一个邮箱。
-
调用bind()绑定服务器的地址(一个众所周知的地址,比如一个公司的邮箱地址,以便允许客户访问)。
-
调用recvfrom()/sendto()接收、发送数据报。
-
调用close()关闭socket
下面创建了一个服务器函数server.c,自定义了一个端口,服务器通过while(1)一直等待客户端的访问,待有客户端访问时,接收其访问读取其ip地址并接收它发送的数据并打印,然后接收等待下一次访问。
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#define PORT_ID 6666
#define SIZE 100
int main(void)
{
int sockfd, client_sockfd;
struct sockaddr_in my_addr, client_addr;
int addr_len;
char buf[SIZE];
char welcome[SIZE] = "Welcome to connect to the sever!";
//init my_addr
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(PORT_ID);
my_addr.sin_addr.s_addr = INADDR_ANY; //any ip addr
//创建socket
sockfd = socket(AF_INET, SOCK_DGRAM, 0);//create a socket
//绑定socket
bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));
addr_len = sizeof(struct sockaddr);
while(1)
{
printf("Server is waiting for client to connect:\n");
//接收信息
recvfrom(sockfd, buf, SIZE, 0, (struct sockaddr *)&client_addr, &addr_len);
printf("Server receive ip is %s, receive data is %s\n", inet_ntoa(client_addr.sin_addr) ,buf);
}
//关闭socket
close(sockfd);
return 0;
}
客户端socket
客户端的流程是:
-
调用socket()创建一个socket,类似于创建一个邮箱。
-
调用recvfrom()/sendto()接收、发送数据报。
-
调用close()关闭socket。
下面创建了一个客户端的程序client.c,主函数设置输入服务器的ip地址,每隔1s发送一次数据,发送五次后关闭socket。
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdlib.h>
#define PORT_ID 6666
#define SIZE 100
int main(int argc, char *argv[])
{
int sockfd;
struct sockaddr_in server_addr;
char buf[SIZE];
int i;
if(argc < 2)
{
printf("Usge:./client [server IP address]\n");
exit(1);
}
//创建socket
sockfd = socket(AF_INET, SOCK_DGRAM, 0);//create a socket
//输入服务器地址
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT_ID);
server_addr.sin_addr.s_addr =inet_addr((argv[1]));
for(i = 0; i < 5; i++)
{
sprintf(buf, "%d\n", i);
//发送数据
sendto(sockfd, buf, SIZE, 0, (struct sockaddr *)&server_addr, sizeof(struct sockaddr));
printf("Client sends to server %s: %s\n", argv[1], buf);
sleep(1);
}
//关闭socket
close(sockfd);
return 0;
}
- 运行结果:由于只有一台电脑,所以服务器和客户端的ip地址是一样的,输入ifconfig,得到ip地址,一个终端运行server.c,此时服务器阻塞,打印"waitting...."后一直等待客户端的访问,另一个终端运行client.c并输入服务器ip地址,并向服务器发送数据,服务器接收访问打印客户端的ip和接收到的数据。