前言
Tcp通信可以说的上是linux中必须要掌握的知识点啦,但是呢往往写这类代码的时候,习惯的敲击ctrl+c来快速的敷衍?
灵魂拷问:你知道你写的每一行代码是干嘛的么?
正文
一、server
流程:创建socket,绑定通信地址,监听设置,等待连接。
怎样查看用到的函数呢?
shell指令: man 2 "函数名" 或 man “函数名"
首先呢,先看一个网络通信的demo
创建socket:TCP/IP 通信,协议族选择PF_INET, 通信类型 选择 SOCK_STREAM
/*
@author: coco
*/
#include <netinet/in.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void err_exit(char *msg)
{
perror(msg);
exit(-1);
}
void main()
{
//创建socket
int sockfd = socket(PF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
err_exit("create socket fail...");
struct sockaddr_in svraddr;
memset(&svraddr, 0, sizeof(svraddr));
svraddr.sin_family = PF_INET;
svraddr.sin_port = htons(5555);
svraddr.sin_addr.s_addr = inet_addr("127.0.0.1");//INADDR_ANY;
//绑定通信地址
int ret = bind(sockfd, (struct sockaddr *)&svraddr, sizeof(svraddr));
if(ret < 0)
err_exit("bind err...");
//监听设置
ret = listen(sockfd, 10000);
struct sockaddr_in newaddr;
int addr_len = sizeof(newaddr);
int maxfd = 0;
while(1)
{
//等待连接
int newfd = accept(sockfd, (struct sockaddr*)&newaddr, &addr_len);
maxfd = maxfd > newfd ? maxfd : newfd;
printf("maxfd fd >> %d\n", maxfd);
}
return;
}
那么问题来了,struct sockaddr_in是什么东东?
他直接用struct sockaddr不香吗?啊?还要强转?
让我们看看结构体sockaddr:
结构体 sockaddr
注释说了,这是一个通用的套接字结构体,通用是啥?晓得嘛?
/* Structure describing a generic socket address. */
//在 /usr/include/x86_64-linux-gnu/bits/socket.h中
#define __SOCKADDR_COMMON_SIZE (sizeof (unsigned short int))
//在 /usr/include/x86_64-linux-gnu/bits/sockaddr.h中
/* Structure describing a generic socket address. */
struct sockaddr
{
__SOCKADDR_COMMON (sa_); /* Common data: address family and length. */
char sa_data[14]; /* Address data. */
};
sa_data又是啥?
再看看结构体sockaddr_in
typedef __u16 __bitwise __be16; //type.h内定义
typedef unsigned short __kernel_sa_family_t
/* Structure describing an Internet (IP) socket address. */
#if __UAPI_DEF_SOCKADDR_IN
#define __SOCK_SIZE__ 16 /* sizeof(struct sockaddr) */
struct sockaddr_in {
__kernel_sa_family_t sin_family; /* Address family */
__be16 sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
/* Pad to size of `struct sockaddr'. */
unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -
sizeof(unsigned short int) - sizeof(struct in_addr)];
};
#define sin_zero __pad /* for BSD UNIX comp. -FvK */
#endif
结构体 in_addr
typedef __u32 __bitwise __be32; //type.h内定义
/* Internet address. */
struct in_addr {
__be32 s_addr; //32位ipv4通信地址 例如"127.0.0.1"
};
看到这里,你应该就能理解,这不就是对sockaddr的补充说明吗?果然够通用。。。
绑定地址bind:结构体sockaddr_in 在 /usr/include/linux/in.h文件内,
sin_family:地址族 //2字节
sin_port:端口 //16位 2字节
sin_addr: 地址 //32位 4字节
__pad(sin_zero): 补充字节 为了和 struct sockaddr同样大小: __SOCK_SIZE__ = 16字节 减去上面三个空间 即剩下(刚好以4字节补充对齐)
等下,,,为什么不能直接用5555 还要加个htons,是脱裤子放屁吗?
这就涉及不同电脑存储字节的方式不同了。
明确两个字节顺序:
网络字节顺序NBO(Network Byte Order):
按从高到低的顺序存储,在网络上使用统一的网络字节顺序,可以避免兼容性问题。
主机字节顺序(HBO,Host Byte Order):
不同的机器HBO不相同,与CPU设计有关,数据的顺序是由cpu决定的,而与操作系统无关。
为了避免两机通信出现,你说英语我说德语,最后干起来的“完美结局”,我们就要统一都讲中国话,因为中国牛逼谢谢。
还有的:
htonl()--"Host to Network Long" ntohl()--"Network to Host Long" htons()--"Host to Network Short" ntohs()--"Network to Host Short"
我们再看一个demo,这是linux自带的一个demo, 不过这是用于本地通信,UDS(Unix Domain Socket),因为如果按进程间通信还要指定IP地址和port端口等。
#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define MY_SOCK_PATH "/somepath"
#define LISTEN_BACKLOG 50
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)
int main(int argc, char *argv[])
{
int sfd, cfd;
struct sockaddr_un my_addr, peer_addr;
socklen_t peer_addr_size;
sfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sfd == -1)
handle_error("socket");
memset(&my_addr, 0, sizeof(struct sockaddr_un));
/* Clear structure */
my_addr.sun_family = AF_UNIX;
strncpy(my_addr.sun_path, MY_SOCK_PATH,
sizeof(my_addr.sun_path) - 1);
if (bind(sfd, (struct sockaddr *) &my_addr,
sizeof(struct sockaddr_un)) == -1)
handle_error("bind");
if (listen(sfd, LISTEN_BACKLOG) == -1)
handle_error("listen");
/* Now we can accept incoming connections one at a time using accept(2) */
peer_addr_size = sizeof(struct sockaddr_un);
cfd = accept(sfd, (struct sockaddr *) &peer_addr,
&peer_addr_size);
if (cfd == -1)
handle_error("accept");
/* Code to deal with incoming connection(s)... */
/* When no longer required, the socket pathname, MY_SOCK_PATH
should be deleted using unlink(2) or remove(3) */
}
先让我们瞅瞅struct sockaddr_un 是什么小妖怪?
/* This macro is used to declare the initial common members
of the data types used for socket addresses, `struct sockaddr',
`struct sockaddr_in', `struct sockaddr_un', etc. */
#define __SOCKADDR_COMMON(sa_prefix) \
sa_family_t sa_prefix##family
定义申明:此宏定义为了满足类型为'struct sockaddr' 'struct sockaddr_in' 以及 'struct sockaddr_un' 等等结构体
以sa_prefix 为前缀 family为后缀。
1.结构体 sockaddr_un
/* Structure describing the address of an AF_LOCAL (aka AF_UNIX) socket. */
struct sockaddr_un
{
__SOCKADDR_COMMON (sun_);
char sun_path[108]; /* Path name. */
};
struct sockaddr_un: __SOCKADDR_COMMON(sun_) 等同 sa_family_t sun_family
struct sockaddr: __SOCKADDR_COMMON(sa_) 等同 sa_family_t sa_family
不同点:在于sa_data 存的是网络地址。 sun_path存的是本地文件地址
额? 不对啊,大兄弟, sockaddr_un 和 sockaddr结构体不一样大啊?
嗯,我想说其实我当初也是这么考虑的,所以有必要解释一下。。。
既然如此就要看看bind函数如何实现的了。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
其实bind函数申明已经能说明问题了,如果协议都一样长,那还要addrlen这个参数干什么?存在即合理啦。好了解释完了。。。
别急。。。我是去下载源码去了,100多M下了我一个多小时。。。
事先说明,这已经要进入内核的范畴了,不过本着通俗易懂,我就说些好理解的东西。
在/usr/src/linux-source-4.15.0/net/socket.c中可以看到一堆函数,统一披着
SYSCALL_DEFINE3的皮囊,嗯这就是系统调用函数。
SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)
{
struct socket *sock;
struct sockaddr_storage address;
int err, fput_needed;
sock = sockfd_lookup_light(fd, &err, &fput_needed);//根据文件描述符查找套接口
if (sock) {
err = move_addr_to_kernel(umyaddr, addrlen, &address);//用户态拷贝到内核
if (err >= 0) {
err = security_socket_bind(sock,
(struct sockaddr *)&address,
addrlen);
if (!err)
err = sock->ops->bind(sock,
(struct sockaddr *)
&address, addrlen);
}
fput_light(sock->file, fput_needed);
}
return err;
}
问题来了 sockaddr_storage 是个啥?
/* Structure large enough to hold any socket address (with the historical
exception of AF_UNIX). */
#define __ss_aligntype unsigned long int
#define _SS_PADSIZE \
(_SS_SIZE - __SOCKADDR_COMMON_SIZE - sizeof (__ss_aligntype))
struct sockaddr_storage
{
__SOCKADDR_COMMON (ss_); /* Address family, etc. */
char __ss_padding[_SS_PADSIZE];
__ss_aligntype __ss_align; /* Force desired alignment. */
};
简单来说,这个东东就是统筹所有协议所需要的空间,他声称他有足够大的空间hold住所有socket地址。
总之就是不用担心空间不同 强转之后造成的问题。
二、client
我直接贴代码了。不想多BB了。
总而言之,就是创建socket,之后连接就完事了。
#include <netinet/in.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
void Sleep(int seconds,int useconds)
{
struct timeval time;
time.tv_sec=seconds;
time.tv_usec=useconds;
select(0,NULL,NULL,NULL,&time);
}
void err_exit(char *msg)
{
perror(msg);
exit(-1);
}
void main()
{
for(int i = 0; i < 10000; i++)
{
Sleep(0,1000);
int sockfd = socket(PF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
err_exit("create socket fail...");
struct sockaddr_in svraddr;
memset(&svraddr, 0, sizeof(svraddr));
svraddr.sin_family = PF_INET;
svraddr.sin_port = htons(5555);
svraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = connect(sockfd, (struct sockaddr*)&svraddr, sizeof(svraddr));
}
return;
}
差不多就这些了,是不是很简单呢?
欢迎指正错误哦~~~