嵌入式Linux网络编程,网络基础,TCP编程,socket(),bind(),listen(),accept(),connect(),send()/recv(),close()/shutdown()

1,创建socket文件描述符socket()

int socket (int domain, int type, int protocol);

  1. domain 是地址族(域名)
domain 含义
PF_NS // Xerox NS协议
PF_IMPLINK // Interface Message协议
AF_INET IPv4 Internet protocols ip(7) // internet 协议
AF_INET6 IPv6 Internet protocols ipv6(7)
AF_UNIX,AF_LOCAL Local communication unix(7)// unix internal协议
AF_NETLINK Kernel user interface device netlink(7)
AF_PACKET Low level packet interface packet(7)
  1. type // 套接字类型
    ·SOCK_STREAM // 流式套接字,唯一对应于TCP
    ·SOCK_DGRAM // 数据报套接字,唯一对应着UDP
    ·SOCK_RAW // 原始套接字
  2. protocol 参数通常置为0,原始套接字编程时需填充
  3. 返回值
    ·On success, a file descriptor for the new socket is returned.
    ·On error, -1 is returned, and errno is set appropriately.
    ·成功时返回文件描述符,出错时返回为-1

2, 绑定bind()

int bind (int sockfd, struct sockaddr* addr, int addrLen);

  1. sockfd 由socket() 调用返回

  2. addr 是指向 sockaddr_in 结构的指针,包含本机IP 地址和端口号
    struct sockaddr_in

    u_short sin_family // protocol family		
    u_short sin_port     // port number
    struct in_addr  sin_addr  //IP address (32-bits)
    
  3. addrLen(地址长度) : sizeof (struct sockaddr_in)

  4. 返回值
    ·On success, zero is returned.
    ·On error, -1 is returned, and errno is set appropriately.

2.1, 地址相关的数据结构struct sockaddr、struct sockaddr_in、struct in_addr

  1. 通用地址结构
  struct sockaddr
  {    
       u_short  sa_family;    //2字节, 地址族, AF_xxx
       char  sa_data[14];     // 14字节协议地址
  };
  1. Internet协议地址结构
  struct sockaddr_in
  {           
       u_short sin_family;      // 地址族, AF_INET,2 bytes
       u_short sin_port;      // 端口,2 bytes
       struct in_addr sin_addr;  // IPV4地址,4 bytes 	
       char sin_zero[8];        // 8 bytes unused,作为填充必须清零
  }; 
  1. IPv4地址结构
// internet address  
struct in_addr
{
     in_addr_t  s_addr;            // u32 network address 
};

3,把主动套接字变成被动套接字listen()

int listen (int sockfd, int backlog);

  1. sockfd:监听连接的套接字,通过socket()函数拿到的fd
  2. backlog
    ·指定了正在等待连接的最大队列长度,它的作用在于处理可能同时出现的几个连接请求。
    ·同时允许几路客户端和服务器进行正在连接的过程(正在三次握手)一般填5, 测试得知,ARM最大为8
    ·DoS(拒绝服务)攻击即利用了这个原理,非法的连接占用了全部的连接数,造成正常的连接请求被拒绝。
  • 内核中服务器的套接字fd会维护2个链表:
    1. 正在三次握手的的客户端链表(数量=2*backlog+1)
    2. 已经建立好连接的客户端链表(已经完成3次握手分配好了newfd)
  1. 返回值: 成功返回0 或 失败返回-1

完成listen()调用后,socket变成了监听socket(listening socket).

4,阻塞等待客户端连接请求accept()

#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) ;

  1. sockfd : 监听套接字 ,经过前面socket()创建并通过bind(),listen()设置过的fd
  2. addr : 对方地址1(内核自动取到连接过来的客户端的信息)
  3. addrlen:地址长度
  4. 返回值:
    ·On success, these system calls return a nonnegative integer that is a descriptor for the accepted socket.
    ·On error, -1 is returned, and errno is set appropriately.
    ·成功时返回已经建立好连接的新的newfd

listen()和accept()是TCP服务器端使用的函数

5,客户端的连接函数connect()

#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

使用方法和服务器的bind()函数类似

  1. sockfd : socket返回的文件描述符
  2. serv_addr : 服务器端的地址信息
  3. addrlen : serv_addr的长度
  4. 返回值:0 或 -1

connect()是客户端使用的系统调用。

6,发送数据send()、write()

send() write()
ssize_t send(int socket, const void *buffer, size_t length, int flags); ssize_t write(int fd, const void *buf, size_t count);

#include <sys/socket.h>
ssize_t send(int socket, const void *buffer, size_t length, int flags);

  1. buffer : 发送缓冲区首地址
  2. length : 发送的字节数
  3. flags : 发送方式(通常为0,填0的时候和write()一样)
flags 含义
MSG_DONTWAIT Enables nonblocking operation;非阻塞发送
MSG_OOB 用以发送TCP类型的带外数据(out-of-band)
  1. 返回值:
    ·成功:实际发送的字节数
    ·失败:-1, 并设置errno

7,接受数据 recv()、read()

recv() read()
ssize_t recv(int socket, const void *buffer, size_t length, int flags); ssize_t read(int fd, void *buf, size_t count);

#include <sys/socket.h>
ssize_t recv(int socket, const void *buffer, size_t length, int flags);

  1. buffer : 发送缓冲区首地址
  2. length : 发送的字节数
  3. flags : 接收方式(通常为0,填0的时候和read()一样)
flags 含义
MSG_DONTWAIT Enables nonblocking operation;非阻塞发送
MSG_OOB 用以发送TCP类型的带外数据(out-of-band)
MSG_PEEK This flag causes the receive operation to return data from thebeginning of the receive queue without removing that data from
the queue. Thus, a subsequent receive call will return the same data.内核会从网络接受数据,并填充缓冲区,read()函数读掉
的数据在缓冲区中就没了;recv()函数用了此参数,读完之后,数据任然存在
  1. 返回值:
    ·成功:实际接收的字节数
    ·失败:-1, 并设置errno

8,套接字的关闭 close()、shutdown()

8.1,关闭双向通讯 close()

int close(int sockfd);

8.2,选择关闭 shutdown()

int shutdown(int sockfd, int howto);

  1. TCP连接是双向的(是可读写的),当我们使用close时,会把读写通道都关闭,有时侯我们希望只关闭一个方向,这个时候我们可以使用shutdown。
  2. howto
howto 含义
SHUT_RD further receptions will be disallowed关闭读通道,但是可以继续往套接字写数据
SHUT_WR further transmissions will be disallowed关闭写通道。只能从套接字读取数据
SHUT_RDWR further recep‐tions and transmissions will be disallowed,和close()效果一样
  1. RETURN VALUE
    ·On success, zero is returned.
    ·On error, -1 is returned, and errno is set appropriately.

9,示例

9.1,头文件<net.h>

#ifndef __NET_H__
#define __NET_H__

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>

#define SERV_PORT 5001
#define SERV_IP_ADDR "192.168.199.200"
#define BACKLOG 5
#define QUIT_STR "quit"



#endif

9.2,服务器端代码<service.c>

#include "net.h"

int main(void)
{
	int fd = -1;
	struct sockaddr_in sin;	//如果是IPV6的编程,要使用struct sockddr_in6结构体(详细情况请参考man 7 ipv6),通常更通用的方法可以通过struct sockaddr_storage来编程
 
	/* 1 创建socket fd */
	if((fd = socket(AF_INET,SOCK_STREAM,0)) < 0){	//AF_INET  IPV4编程
		perror("sockket");
		exit(1);
	}
	/* 2 绑定 */
	/* 2.1 填充struct sockaddr_in结构体变量 */
	bzero(&sin,sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons(SERV_PORT);//网络字节序的端口号
	/* 优化1  让服务器可以绑定在任意的IP上 */
#if 1
	sin.sin_addr.s_addr = htonl(INADDR_ANY);// 优化1  让服务器可以绑定在任意的IP上 
#else
	if(inet_pton(AF_INET,SERV_IP_ADDR,(void *)sin.sin_addr.s_addr) < 0){	//sin.sin_addr.s_addr等价于sin.sin_addr
																	//AF_INET  IPV4编程
		perror("inet_pton");
		exit(1);
	}
#endif
	/* 2.2 绑定 */
	if(bind(fd,(struct sockaddr *)&sin,sizeof(sin)) < 0){
		perror("bind");
		exit(1);
	}
	/* 3 调用listen() 把主动套接字变成被动套接字 */
	if(listen(fd,BACKLOG) <0){
		perror("listen");
		exit(1);
	}
	int newfd = -1;
	/* 4 阻塞等待客户端连接请求 */
#if 0
	if((newfd = accept(fd,NULL,NULL)) < 0)//不关心客户端信息,来了就为其服务
	{
		perror("accept");
		exit(1);
	}
	/* 优化2 通过程序获取刚建立连接的socket的客户端的IP地址和端口号 */
#else
	struct sockaddr_in cin;
	socklen_t addrlen  = sizeof(cin);
	if((newfd = accept(fd,(struct sockaddr *)&cin,&addrlen)) < 0)
	{
		perror("accept");
		exit(1);
	}
	char ipv4_addr[16];
	if(! inet_ntop(AF_INET,(void *)&cin.sin_addr.s_addr,ipv4_addr,sizeof(cin)))//将网络字节序形式的IP地址转化为本地点分形式的字符串IP地址
	{
		perror("inet_ntop");
		exit(1);
	}

	printf("Client (:%s is connected port:%d\n",ipv4_addr,ntohs(cin.sin_port));
#endif	
	/* 5 读写 */
	int ret = -1;//read()是个阻塞函数,要做读写错误的工程处理
	char buf[BUFSIZ];//BUFSIZE是系统提供的
	
	while(1)
	{
		bzero(buf,BUFSIZ);//首先将buf清零
		do{
			ret = read(newfd,buf,BUFSIZ-1);//防止数组下标越界BUFSIZE-1
		}while(ret < 0 && EINTR == errno);
		if(ret < 0)
		{
			perror("read");
			exit(1);
		}
		if(!ret){	//对方已经关闭
			break;
		}
		printf("receive data: %s",buf);
		if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR))){	//用户输入了quit字符
			printf("Client is existing!\n");
			break;
		}
	}
	close(newfd);
	close(fd);
	return 0;
}

9.3,客户端代码<client.c>

#include "net.h"

int main(void)
{
	int fd = -1;
	struct sockaddr_in sin;	//如果是IPV6的编程,要使用struct sockddr_in6结构体(详细情况请参考man 7 ipv6),通常更通用的方法可以通过struct sockaddr_storage来编程
 
	/* 1 创建socket fd */
	if((fd = socket(AF_INET,SOCK_STREAM,0)) < 0){	//AF_INET  IPV4编程
		perror("sockket");
		exit(1);
	}
	/* 2 连接服务器 */
	/* 2.1 填充struct sockaddr_in结构体变量 */
	bzero(&sin,sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_port = htons(SERV_PORT);//网络字节序的端口号
#if 1
	if((sin.sin_addr.s_addr = inet_addr(SERV_IP_ADDR)) < 0){
		perror("inet_addr");
		exit(1);
	}
#else
	if(inet_pton(AF_INET,SERV_IP_ADDR,(void *)sin.sin_addr.s_addr) < 0){	//sin.sin_addr.s_addr等价于sin.sin_addr
																	//AF_INET  IPV4编程
		perror("inet_pton");
		exit(1);
	}
#endif
	/* 2.2 连接服务器 */
	if(connect(fd,(struct sockaddr *)&sin,sizeof(sin)) < 0){
		perror("connect");
		exit(1);
	}
	
	/* 3 读写数据 */
	char buf[BUFSIZ];//BUFSIZE是系统提供的
	
	while(1)
	{
		bzero(buf,BUFSIZ);//首先将buf清零
		if(fgets(buf,BUFSIZ-1,stdin) == NULL)//放置数组下标越界BUFSIZE-1
		{
			continue;
		}
		write(fd,buf,strlen(buf));
		if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR))){	//用户输入了quit字符
			printf("Client is existing!\n");
			break;
		}
	}
	/* 4 关闭套接字 */
	close(fd);
	return 0;
}

9.4,运行客户端

linux@linux:~/test/network$ ./client
asda
axsa
as
quite
Client is existing!

9.5,运行服务器端

linux@linux:~/test/network$ ./service
Client (:192.168.199.200 is connected port:44644
receive data: asda
receive data: axsa
receive data: as
receive data: quite
Client is existing!

猜你喜欢

转载自blog.csdn.net/m0_37542524/article/details/84108235