嵌入式Linux网络编程,网络基础,TCP编程,socket(),bind(),listen(),accept(),connect(),send()/recv(),close()/shutdown()
文章目录
1,创建socket文件描述符socket()
int socket (int domain, int type, int protocol);
- 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) |
- type // 套接字类型
·SOCK_STREAM // 流式套接字,唯一对应于TCP
·SOCK_DGRAM // 数据报套接字,唯一对应着UDP
·SOCK_RAW // 原始套接字 - protocol 参数通常置为0,原始套接字编程时需填充
- 返回值
·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);
-
sockfd 由socket() 调用返回
-
addr 是指向 sockaddr_in 结构的指针,包含本机IP 地址和端口号
struct sockaddr_inu_short sin_family // protocol family u_short sin_port // port number struct in_addr sin_addr //IP address (32-bits)
-
addrLen(地址长度) : sizeof (struct sockaddr_in)
-
返回值
·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
- 通用地址结构
struct sockaddr { u_short sa_family; //2字节, 地址族, AF_xxx char sa_data[14]; // 14字节协议地址 };
- 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,作为填充必须清零 };
- IPv4地址结构
// internet address struct in_addr { in_addr_t s_addr; // u32 network address };
3,把主动套接字变成被动套接字listen()
int listen (int sockfd, int backlog);
- sockfd:监听连接的套接字,通过socket()函数拿到的fd
- backlog
·指定了正在等待连接的最大队列长度,它的作用在于处理可能同时出现的几个连接请求。
·同时允许几路客户端和服务器进行正在连接的过程(正在三次握手)一般填5, 测试得知,ARM最大为8
·DoS(拒绝服务)攻击即利用了这个原理,非法的连接占用了全部的连接数,造成正常的连接请求被拒绝。
- 内核中服务器的套接字fd会维护2个链表:
1. 正在三次握手的的客户端链表(数量=2*backlog+1)
2. 已经建立好连接的客户端链表(已经完成3次握手分配好了newfd)
- 返回值: 成功返回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) ;
- sockfd : 监听套接字 ,经过前面socket()创建并通过bind(),listen()设置过的fd
- addr : 对方地址1(内核自动取到连接过来的客户端的信息)
- addrlen:地址长度
- 返回值:
·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()函数类似
- sockfd : socket返回的文件描述符
- serv_addr : 服务器端的地址信息
- addrlen : serv_addr的长度
- 返回值: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);
- buffer : 发送缓冲区首地址
- length : 发送的字节数
- flags : 发送方式(通常为0,填0的时候和write()一样)
flags | 含义 |
---|---|
MSG_DONTWAIT | Enables nonblocking operation;非阻塞发送 |
MSG_OOB | 用以发送TCP类型的带外数据(out-of-band) |
- 返回值:
·成功:实际发送的字节数
·失败:-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);
- buffer : 发送缓冲区首地址
- length : 发送的字节数
- 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, 并设置errno
8,套接字的关闭 close()、shutdown()
8.1,关闭双向通讯 close()
int close(int sockfd);
8.2,选择关闭 shutdown()
int shutdown(int sockfd, int howto);
- TCP连接是双向的(是可读写的),当我们使用close时,会把读写通道都关闭,有时侯我们希望只关闭一个方向,这个时候我们可以使用shutdown。
- 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()效果一样 |
- 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!阅读更多
- Linux网络协议栈之TCP socket/bind/listen/connect/accept/close/shutdown
- linux网络编程二:基础socket, bind, listen, accept, connect
- linux网络编程常用函数详解与实例(socket-->bind-->listen-->accept)
- linux中TCP的socket、bind、listen、connect和accept的实现
- linux网络编程常用函数详解与实例(socket-->bind-->listen-->accept)
- linux网络编程常用函数详解与实例(socket-->bind-->listen-->accept)
- linux网络编程常用函数详解与实例(socket-->bind-->listen-->accept)
- Linux进程间通信(八):流套接字 socket()、bind()、listen()、accept()、connect()、read()、write()、close()
- linux网络编程常用函数详解与实例(socket-->bind-->listen-->accept)
- linux网络编程常用函数详解与实例(socket-->bind-->listen-->accept)
- linux网络编程常用函数详解与实例(socket-->bind-->listen-->accept)
- 【Linux网络编程】TCP网络编程中connect()、listen()和accept()三者之间的关系
- linux网络编程常用函数详解与实例(socket-->bind-->listen-->accept)
- 网络socket编程指南 4 listen accept send recv 函数
- Linux进程间通信(八):流套接字 socket()、bind()、listen()、accept()、connect()、read()、write()、close()
- socket编程总结(七)int socket/connect()/send()/recv()/close()/shutdown()/recvfrom
- SOCKET API和TCP STATE的对应关系__三次握手(listen,accept,connect)__四次挥手close及TCP延迟确认(调用一次setsockopt函数,设置TCP_QUI
- Linux下Socket网络编程send和recv使用注意事项
- Linux下Socket网络编程send和recv使用注意事项
- Socket通信——Linux下,socket、bind、listen、accept、connect的含义