Linux应用学习_网络开发
1. TCP套接字编程
1.1 套接字
1.1.1 通用套接字sockaddr
struct sockaddr {
sa_family_t sa_family; /* address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
};
typedef unsigned short sa_family_t
sockaddr
共占用16+14=30个字节- 但是使用
sockaddr
结构体不方便进行设置,因此一般采用struct sockaddr_in
1.1.2 常用套接字 sockaddr_in
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)];
};
struct in_addr{ /*IP地址结构*/
u32 s_addr; /*32位IP地址,网络字节序*/
}
1.1.3 两个套接字的对应关系
- 两个套接字都是32个字节,大小一样,因此在使用过程中可以通过设置
sockaddr_in
然后强制类型转换为sockaddr
2. TCP——服务器
2.1 程序设计流程
- 套接字初始化
socket
- 套接字与端口绑定
bind
- 设置服务器的侦听连接
listen
- 接受客户端连接
accept
- 接受数据
read
- 发送数据
write
- 关闭套接字
close
2.1.1 socket
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain
用于设置网络通信的域PF_UNIX,PF_LOCAL
本地通信PF_INET
IPv4Internet协议(有时用的AF_INET
,两个值一样)PF_INET6
IPv6Internet协议
tpye
设置套接字通信的类型SOCK_STREAM
TCP连接,提供序列化的、可靠的、双向连接的字节流SOCK_DGRAM
UDP连接
protocol
用于指定某个协议的特定类型- 通常协议中只有一个特定类型,这样只能设置为0
- 返回值为一个
new socket
常用形式:
int sockfd;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
return sockfd;
}
2.1.2 bind
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
sockfd
指向socket()
函数创建的文件描述符sockaddr *addr
指向一个结构体,包含地址、端口和IP地址信息addrlen
结构体的长度,一般设置为sizeof(struct sockaddr)
- 返回值为0表示绑定成功,-1表示绑定失败
常用形式:
int ret;
struct sockaddr_in my_addr;
bzero(&my_addr, sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(8888);
my_addr.sin_addr.s_addr = inet_addr("192.168.9.99");
ret = bind(sockfd, (struct sockaddr *)(&my_addr), sizeof(struct sockaddr ));
if (ret < 0)
{
return ret;
}
htons
将端口地址进行字节序转换inet_addr()
将字符串转换为二进制网络字节序的IP地址值
2.1.3 listen
函数listen()
用来初始化服务器可连接队列,服务器处理客户端连接请求的时候是顺序处理的,同一时间只能处理一个客户端连接。当多个客户端的连接请求同时到来的时候,服务器并不是同时处理,而是将不能处理的客户端连接请求放到等待队列中,这个队列的长度有listen()
函数来定义。
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
sockfd
指向socket()
函数创建的文件描述符backlog
可连接数量listen()
函数仅对类型为SOCK_STREAM
和SOCK_SEQPACkET
的协议有效- 运行成功返回0,失败返回-1
常用形式:
ret = listen(sockfd, 20);
if (ret < 0)
{
return ret;
}
2.1.4 accept
通过accept()
函数可以得到成功连接的客户端的IP地址、端口和协议族等信息。
accept()
函数的返回值是新连接的客户端套接字文件描述符,与客户端之间的通信是通过accept()
函数返回的新套接字文件描述符来进行的,而不是通过建立套接字时的文件描述符,这是程序设计的时候需要注意的地方。
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- 返回值,当成功返回时,返回的是新连接的客户端的文件描述符,错误返回-1
addr
新连接的客户端的相关信息,包括IP地址、端口和协议族信息addrlen
通常设置为sizeof(struct addr)
常用形式:
int sockfd;
sockfd = Server_Init();
struct sockaddr_in client_info;
int client_info_len = sizeof(struct sockaddr_in);
while (1)
{
int client_fd = accept(sockfd , &client_info, &client_info_len);
if (client_fd == -1)
{
perror("accept error\r\n");
exit(EXIT_FAILURE);
}
printf("clinet_ip:%s\r\n",inet_ntoa(client_info.sin_addr));
printf("client_port:%d\r\n",ntohs(client_info.sin_port));
}
inet_ntoa
ntohs
2.1.5 read
read()
函数可以从套接字描述符中读取数据
int size;
char data[1024];
size = read(fd, data, 1024)
2.1.6 write
- 当服务器在接收到一个客户端的连接后,可以通过套接字描述符进行数据的写入操作
- 写入形式和过程与普通文件的操作方式一致
int size;
char data[1024];
size = write(fd, data, 1024);
2.1.7 close
close()
可以关闭已经打开的socket- 可以通过
shutdown()
可以使用更多方式来关闭连接,允许单方面切断通信或者切断双方的通信
#include <sys/socket.h>
int shutdown(int sockfd, int how);
sockfd
要关闭的套接字how
表示切断方式SHUT_RD
切断读,之后不能使用此文件描述符进行读操作SHUT_WR
切断写,之后不能使用此文件描述符进行写操作SHUT_RDWR
切断读写
3. TCP——客户端
3.1 程序设计流程
- 套接字初始化函数
socket()
- 客户端连接服务器
connect()
- 接受数据
read()
- 发送数据
write()
- 套接字关闭
close()
3.1.1 socket
同服务器socket
3.1.2 connetc
客户端在建立套接字之后,不需要进行绑定地址就可以通过 connect
直接连接服务器,此函数连接指定参数的服务器,例如IP地址、端口等
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
sockfd
创建的套接字addr
客户端需要连接的服务器的目的端口和IP地址addrlen
通常设置为sizeof(struct sockaddr)
- 连接成功时返回0,失败时返回-1
常用形式:
int sockfd;
int ret;
struct sockaddr_in server_info;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
perror("sockfd failed\r\n");
return sockfd;
}
bzero(&server_info,sizeof(struct sockaddr_in));
server_info.sin_family = AF_INET;
server_info.sin_port = htons(port);
server_info.sin_addr.s_addr = htonl(ip);
ret = connect(sockfd, &server_info, sizeof(struct sockaddr_in));
if (ret < 0)
{
perror("connetc failed\r\n");
return ret;
}
3.1.3 read
同服务器read
3.1.4 write
同服务器write
3.1.5 close
同服务器关闭
4. TCP 服务器客户端编程注意事项
4.1 SIGPIPE
如果正在写入套接字的时候,当读取端已经关闭时,可以得到一个SIGPIPE信号。信号会终止当前进程,因为信号系统在调用系统默认处理方式之前会调用用户注册的函数,所以可以通过注册SIGPIPE信号的处理函数来获取这个信号,并进行相应的处理。例如,当服务器已经关闭,而客户端试图向套接字写入数据的时候会产生一个SIGPIPE信号,此时会稻城程序的非正常退出。
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
常用形式:
void sigpipe_handler(int sign)
{
printf("catch a pipe signal");
/*free*/
}
signal(SIGPIPE, sigpipe_handler);
的时候会产生一个SIGPIPE信号,此时会稻城程序的非正常退出。
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
常用形式:
void sigpipe_handler(int sign)
{
printf("catch a pipe signal");
/*free*/
}
signal(SIGPIPE, sigpipe_handler);