前言:
对于APUE网络socket,我们需要了解就是server和client之间的通信建立过程。首先我们需要知道两台不同网段的pc是怎么通信的,当一台pc通过应用程序发一段消息过来给另一台pc时,pc获取到数据包之后就行拆包解析,这些都是操作系统内核的工作,对于server而言,需要告诉内核使用什么ip和端口来建立起服务,当有client接入时,内核先进行解析,得到client的ip和访问端口之后再给server(对应的应用程序),最后完成通信建立;对于client而言,需要知道server的ip和端口,获取ip来源主要是通过dns解析和人为获取,然后通过获取的ip和端口进行发送连接请求,最后完成通信。
1、socket()建立好通讯协议
int socket(int domain, int type, int protocol);
(1)domain:包含了ipv4和ipv6的协议族,AF_INET(ipv4)AF_INET6(ipv6)、
AF_LOCA(或称AF_UNIX,Unix域socket)、AF_ROUTE等等
(2)type:指定socket类型。常用的socket类型有,SOCK_STREAM(TCP流)、SOCK_DGRAM(UDP流)、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等。
(3)protocol:指定协议。常用的协议有IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC、它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议,type和protocol并不是可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。
socket_fd = socket(AF_INET, SOCK_STREAM, 0);内核的返回值为创建一个socket的描述符,socket_fd;
2、bind()提供服务器的ip和端口
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
addrlen:对应的是地址的长度。
addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同,但最终都会强制转换后赋值给sockaddr这种类型的指针传给内核。
bind返回值 < 0 则失败,>0 成功
这里涉及了几个结构体、通用套接字类型,ipv4类型和ipv6类型,不管是ipv4还是ipv6,在传数据给内核时都要使用通用型的,所以数据需要强行转换。
#define LISTEN_PORT 8889
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(LISTEN_PORT); //操作系统的字节序为小端字序,所以需要转换成网络的大端字节序,端口为short型,用htons
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); //ip地址为4个字节,用htonl;INADDR_ANY(实际上是0)可以自动选择服务器的ip,使用有线网卡就用有线ip,使用无线网卡就用无线ip。
bind(sock_fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)); //不管是ipv4还是ipv6,在传数据给内核时都要使用通用型的,所以数据需要强行转换。
通用型
typedef unsigned short int sa_family_t;
struct sockaddr {
sa_family_t sa_family; /* 2 bytes address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
}
ipv4
typedef unsigned short sa_family_t;
typedef uint16_t in_port_t;
struct in_addr {
uint32_t s_addr;
};
struct sockaddr_in {
sa_family_t sin_family; /* 2 bytes address family, AF_xxx such as AF_INET */
in_port_t sin_port; /* 2 bytes port*/
struct in_addr sin_addr; /* 4 bytes IPv4 address*/
/* Pad to size of `struct sockaddr'. */unsigned char sin_zero[8]; /* 8 bytes unused padding data, always set be zero */
};
3、listen()监听socket,等待客户端连接
这个用来监听socket,等待客户端连接;通过前面两个步骤,应用程序已经告诉内核要使用什么通信协议,用什么ip和端口来建立服务器,这个时候就等待有谁来进行访问就可以了,相当于打开了这个应用程序,比如QQ的聊天一样。
int listen(int sockfd, int backlog);
sockefd: socket()系统调用创建的要监听的socket描述字
backlog: 相应socket可以在内核里排队的最大连接个数,比如 13,正在连接服务器的最大数只能是13,超过就连不上。
#define BACKLOG 13
listen(listen_fd, BACKLOG);
4、accept()
接受客户端的连接请求,同时产生新的socket描述符,用于服务某个客户端,一对一的关系。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
这里的意思是,当服务器接受到连接请求时,收到的数据包首先是经过操作系统处理好的,所以要想应用程序知道客户端的ip和端口,需要向内核拿数据,accept函数就是处理好这一部分工作。
sockfd: 服务器开始调用socket()函数生成的,称为监听socket描述字;
*addr: 用于返回客户端的协议地址,这个地址里包含有客户端的IP和端口信息等;
addrlen: 返回客户端协议地址的长度
返回值为一个新的socket文件描述符,这个用来与客户端之间相互通信。
int client_fd =-1;
struct sockaddr_in clientaddr;
client_fd = accept(listen_fd, (struct sockaddr*)&clientaddr, &cliaddr_len);
一般来说都不关心这个长度,这个数据不重要。地址为ipv4类型,所以需要保持一致。内核返回的类型是通用类型的。
5、read()/write()
最后开始进行读写数据
请参考博客:https://blog.csdn.net/weixin_45062087/article/details/118880374
1、socket()
请参考server部分的socket()
2、connect()
TCP客户端程序调用socket()创建socket fd之后,就可以调用connect()函数来连接服务器。如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求并使accept()返回,accept()返回的新的文件描述符就是对应到该客户的TCP连接,通过这两个文件描述符(客户端connect的fd和服务器端accept返回的fd)就可以实现客户端和服务器端的相互通信。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd: 客户端的socket()创建的描述字
addr: 要连接的服务器的socket地址信息,这里面包含有服务器的IP地址和端口等信息
addrlen: socket地址的长度
3、read()/write()
最后开始进行读写数据
请参考博客:https://blog.csdn.net/weixin_45062087/article/details/118880374
1、server
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//#define PORT 9988
void printf_usage(char *progname)
{
printf("%s usage: \n",progname);
printf("-p(--port): sepcify server port \n");
printf("-h(--help): print this help information \n");
}
int main(int argc, char **argv)
{
int socket_fd = -1, client_fd = -1;
int rv = -1, rv1 = -1;
struct sockaddr_in serveraddr, clientaddr;
char buf[1024];
socklen_t clientaddr_len = sizeof(struct sockaddr);
int port;
int ch;
int index;
struct option opts[] = {
{"port", required_argument, NULL, 'p'},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0}
};
while((ch = getopt_long(argc, argv, "p:h", opts, NULL)) != -1)
{
switch(ch)
{
case 'p':
port = atoi(optarg);
break;
case 'h':
printf_usage(argv[0]);
return 0;
}
}
while(argc < 2)
{
printf("please input %s [port] \n",argv[0]);
return -1;
}
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if(socket_fd < 0)
{
printf("create socket failure : %s \n", strerror(errno));
return -2;
}
port = atoi(argv[1]);
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(port); //操作系统的字节序为小端,所以需要转换成网络大端字节序
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); //ip地址为4个字节,用htonl;INADDR_ANY(实际上是0)可以自动选择服务器的ip,使用有线网卡就用有线ip,使用无线网卡就用无线ip。
if(bind(socket_fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
{
printf("create server failure : %s \n", strerror(errno));
return -3;
}
printf("socket [%d] bind on port[%d] for all IP address succesfully !", socket_fd, port);
listen(socket_fd, 10); //监听socket,等待客户端连接,10为正在连接服务端的最大数,超过就不能连入服务器。
while(1)
{
printf("\n Start waiting and accept new client connect...\n");
client_fd = accept(socket_fd, (struct sockaddr *)&clientaddr, &clientaddr_len); //一般来说不关心addr的长度大小,这个数据不重要;地址为ipv4类型,需要保持一致;内核返回的值为新的socket描述符,用来服务某个接入的客户端。
if(client_fd < 0)
{
printf("accept new socket failture : %s\n", strerror(errno));
return -4;
}
printf("accept new client[%d] socket[%s:%d]\n",client_fd, inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));//inet_ntoa函数为整型转字符型,ntohs函数为大端转小端。
while(1)
{
memset(buf, 0, sizeof(buf));
rv = read(client_fd, buf, sizeof(buf));
if(rv < 0)
{
printf("read data from client [%d] failure : %s\n", client_fd, strerror(errno));
close(client_fd);
break;
}
else if(rv == 0)
{
printf("%d \n",rv);
printf("client [%d] disconnected \n", client_fd);
close(client_fd);
break;
}
printf("read %d bytes data from client[%d] and the data is : %s \n", rv , client_fd, buf);
rv1 = write(client_fd, buf, rv);
if(rv1 < 0)
{
printf("write data back to client[%d] failure: %s \n", client_fd, strerror(errno));
close(client_fd);
break;
}
}
sleep(1);
}
close(socket_fd);
}
2、client
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//#define SERVER_IP "127.0.0.1"
//#define SERVER_PORT 9988
#define MSG_STR "Hello,server"
void printf_usage(char *progname)
{
printf("%s usage: \n",progname);
printf("-i(--ipaddr): sepcify server IP address \n");
printf("-p(--port): sepcify server port \n");
printf("-h(--help): print this help information \n");
}
int main(int argc, char **argv)
{
int connt_fd = -1;
int rv = -1;
struct sockaddr_in serveraddr;
char buf[1024];
char *serverip;
int port;
int ch;
int index;
struct option opts[] = {
{"ipaddr", required_argument, NULL, 'i'},
{"port", required_argument, NULL, 'p'},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0}
};
while((ch = getopt_long(argc, argv, "i:p:h", opts, NULL)) != -1)
{
switch(ch)
{
case 'i':
serverip = optarg;
break;
case 'p':
port = atoi(optarg);
break;
case 'h':
printf_usage(argv[0]);
return 0;
}
}
while(1)
{
while(argc < 3)
{
printf("please input %s [serverip] [port]\n",argv[0]);
return -1;
}
connt_fd = socket(AF_INET, SOCK_STREAM, 0);
if(connt_fd < 0)
{
printf("create connt_fd failure: %s!\n", strerror(errno));
close(connt_fd);
return -2;
}
printf("create connt_fd[%d] successfully! \n", connt_fd);
//serverip = argv[1];
//port = atoi(argv[2]);
memset(&serveraddr, 0 ,sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(port);
inet_aton(serverip, &serveraddr.sin_addr);
if(connect(connt_fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
{
printf("connect to server[%s:%d] faiture : %s!\n",serverip, port, strerror(errno));
close(connt_fd);
return -3;
}
printf("connect to server[%s:%d] successfully!\n", serverip, port);
while(1)
{
if(write(connt_fd, MSG_STR, strlen(MSG_STR)) < 0)
{
printf("write the data to buf failture: %s", strerror(errno));
goto cleanup;
}
memset(buf, 0, sizeof(buf));
rv = read(connt_fd, buf ,sizeof(buf));
if(rv < 0)
{
printf("read the data from server failure: %s", strerror(errno));
goto cleanup;
}
if(rv == 0)
{
printf("disconnect the server....\n");
goto cleanup;
}
printf("read %d bytes from server : '%s' \n", rv, buf);
sleep(1);
}
cleanup:
close(connt_fd);
}
}
整体来说,需要了解server、client是怎么建立起来的,用了哪些函数。这里通过了一个ipv4和TCP协议建立起双方通信的协议,socket是介于应用层和传输层之间的接口,我们只需要通过系统调用生成socket的文件描述符,用于双方进行通信,有不足之处请各位大佬在评论区留言,谢谢。