文章目录
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!