linux——最简单的Tcp通信

前言

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;

}

差不多就这些了,是不是很简单呢?

 

 

欢迎指正错误哦~~~

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值