首先介绍Linux下整个的网络编程流程:
一、socket地址API
1.主机字节序和网络字节序
字节序分为大端字节序(big endian)和小端字节序(little endian)。大端字节序是指一个整数的搞我字节存储在内存的低地址处,低位字节存储在内存的高地址处。小端字节序则是整数的高位字节存储在内存的高地址处,而低位字节则存储在内存的低地址处。
一般,大端字节序也称为网络字节序,小端字节序也称为主机字节序。
#include <netinet/in.h>
unsigned long int htonl(unsigned long int hostlog);//将长整型的主机字节序数据转为网络字节序数据
unsigned short int htons(unsigned short int hostshort); //
unsigned long int ntohl(unsigned long int netlong);
unsigned short int ntohs(unsigned short int netshort);
2.通用socket地址
#include <bits/socket.h>
struct sockaddr
{
sa_family_t sa_family; //地址族
char sa_data[14];
};
3.专用socket地址
上面这个通用socket地址结构体显然很不好用,比如设置与获取IP地址和端口号就需要执行繁琐的位操作。TCP/IP协议族有sockaddr_in和sockaddr_in6两个专用socket地址结构体,他们分别用于IPv4和IPv6,这里只介绍常用的IPv4socket地址:
struct sockaddr_in
{
sa_family_t sin_family; //地址族,AF_INET
u_int16_t sin_port; //端口号
struct in_addr sin_addr; //IPv4结构体
};
struct in_addr
{
u_int32_t s_addr; //IPv4地址,要用网络字节序表示
};
所有专用socket地址类型的变量在实际使用时都需要转化为通用socket地址类型sockaddr(强制类型转换),因为所有socket编成接口使用的地址参数类型都是sockaddr。
4.IP地址转换函数
#include <arpa/inet.h>
//将用点分十进制字符串表示的IPv4地址转化为网络字节序整数表示的IPv4地址,失败时返回INADDR_NONE.
in_addr_t inet_addr(const char* strptr);
//将点分十进制字符串表示的IPv4地址转化为网络字节序表示的IPv4地址,存放于inp中
int inet_aton(const char *cp, struct in_addr *inp);
//将用网络字节序整数表示的IPv4地址转化为用点分十进制字符串表示的IPv4地址
char* inet_ntoa(struct in_addr in);
//将字符串表示的IP地址src转为网络字节序整数表示的IP地址,并存放在dst中,其中af指定地址族
int inet_pton(int af, const char* src, void *dst);
//与上面相反
const char* inet_ntop(int af, const void* src, char* dst, socklen_t cnt);
二、socket基础API
1.创建socket
socket是一个可读、可写、可控制和可关闭的文件描述符。下面的socket系统调用创建一个socket:
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
参数:
domain :指定协议族,一般用PF_INET
type :指定服务类型,主要有SOCK_STREAM服务(流服务)和SOCK_UGRAM(数据报)服务
protocol:一般设置为0,表示使用默认协议。因为前两个参数已经唯一确定了是使用TCP还是UDP协议
返回值:
socket系统调用成功时返回一个socket文件描述符,失败则返回-1并设置errno.
2.命名socket
创建socket时,指定了地址族,但是没有指定具体使用哪个socket地址,将一个socket与socket地址绑定称为给socket命名。在服务器程序中要命名socket,因为只有命名后客户端才能知道该如何连接它。客户端通常不需要命名socket,而是采用匿名方式,即使用操作系统自动分配的socket地址。
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr* my_addr, socklen_t addrlen);
功能:
将my_addr所指的socket地址分配给未命名的sockfd
参数:
sockfd :调用socket()创建的socket文件描述符
my_addr:socket地址
addrlen:my_addr的长度
返回值:
bind成功时返回0,失败则返回-1,并设置errno.其中两种常见的errno是EACCES和EADDRINUSE,EACCES是指被绑定的地址是受保护的
地址,而EADDRINUSE是指被绑定的地址正在使用中。
3.监听socket
socket被命名以后,还不能马上接受客户连接,需要使用如下系统调用创建一个监听队列以存放待处理的客户连接:
#include <sys/socket.h>
int listen(int sockfd, int backlog);
功能:
创建一个监听队列用于存放待处理的连接
参数:
sockfd :指定被监听的socket
backlog:指示内核监听队列的最大长度
返回值:
listen成功时返回0,失败则返回-1并设置errno。
4.接收连接
每当连接到来时就会被放入listen()系统调用创建的监听队列中,这时需要调用accept()从监听队列中接受一个连接:
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:
从listen监听队列中接受一个连接
参数:
sockfd :执行过listen系统调用的监听socket。
addr :用于获取被接受连接的源端socket地址
addrlen:指定addr的长度
返回值:
成功时返回一个新的连接socket,该socket唯一地标识了被接受的这个连接,服务器可以通过读写该socket来与被接受连接的客户端通信。失败时返回-1并设置errno.
5.发起连接
服务器通过listen调用来被动接受连接,那么客户端需要通过connect()系统调用来与服务器建立连接:
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *servAddr, socklen_t addrlen);
功能:
与服务器建立连接
参数:
sockfd :由socket()系统调用返回
servAddr:服务器监听的socket地址
addrlen :指定servAddr的长度
返回值:
connect成功时返回0.一旦成功建立连接,sockfd就唯一标识了这个连接,客户端就可以通过读写该sockfd来与服务器通信。connect失败时返回-1并设置errno,两种常见的errno是ECONNREFUSED和ETIMEOUT,ECONNREFUSED表示目标端口不存在,ETIMEOUT表示连接超时。
6.关闭连接
关闭一个连接实际上是关闭该连接对应的socket。
#include <unistd.h>
int close(int fd);
功能:
关闭文件描述符。close系统调用并非总是关闭一个连接,而是将fd引用计数减1,只有当fd引用计数为0时,才真正关闭连接。
参数:
fd:指定要关闭的文件描述符
返回值:
成功时返回0,失败时返回-1并设置errno.
三、数据读写
1.TCP数据读写
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能:
从sockfd中读取len字节的数据到buf中
参数:
sockfd:建立连接后的文件描述符
buf :读缓冲区的位置
len :读缓冲区的大小
flags :一般设置为0
返回值:
recv成功时返回实际读取到的数据的长度,它可能返回0,这意味着通信对方已经关闭连接,出错时返回-1并设置errno.
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能:
将buf中len字节数据写入sockfd中
参数:
sockfd:建立连接后的文件描述符
buf :写缓冲区的位置
len :写缓冲区的大小
flags :一般设置为0
返回值:
send成功时返回实际写入数据的长度,失败时则返回-1并设置errno.
2.UDP数据读写
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *srcaddr, socklen_t *addrlen);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
struct sockaddr *destaddr, socklen_t addrlen);
3.通用数据读写函数
#include <sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr* msg, int flags);
ssize_t sendmsg(int sockfd, struct msghdr* msg, int flags);
4.socket选项
socket选项 | 作用 |
SO_REUSEADDR | 强制使用被处于TIME_WAIT状态的连接占用的socket地址 |
SO_RCVBUF | 设置接收缓冲区大小 |
SO_SNDBUF | 设置发送缓冲区大小 |
SO_RCVLOWAT | 接收缓冲区的低水位标记 |
SO_SNDLOWAT | 发送缓冲区的低水位标记 |
SO_LINGER | 控制close()系统调用在关闭TCP连接时的行为 |
四、实例代码分析
客户端代码:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main( int argc, char* argv[] )
{
if( argc <= 2 )
{
printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );
return 1;
}
const char* ip = argv[1];
int port = atoi( argv[2] );
struct sockaddr_in server_address;
bzero( &server_address, sizeof( server_address ) );
server_address.sin_family = AF_INET;
inet_pton( AF_INET, ip, &server_address.sin_addr );
server_address.sin_port = htons( port );
int sockfd = socket( PF_INET, SOCK_STREAM, 0 ); //创建socket,使用TCP协议
assert( sockfd >= 0 );
//发起连接
int ret = connect( sockfd, (struct sockaddr*)&server_address, sizeof(server_address));
if(ECONNREFUSED == ret)
{
printf("目标端口不存在,连接被拒绝.\n");
}
else if(ETIMEOUT == ret)
{
printf("连接超时.\n");
}
else if(ret < 0)
{
printf("connetion failed.\n");
}
else
{
printf( "send oob data out\n" );
const char* normal_data = "123";
send( sockfd, normal_data, strlen( normal_data ), 0 ); //发送数据
}
close( sockfd ); //关闭连接
return 0;
}
服务端代码:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
const int BUF_SIZE = 1024;
int main( int argc, char* argv[] )
{
if( argc <= 2 )
{
printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );
return 1;
}
const char* ip = argv[1];
int port = atoi( argv[2] );
struct sockaddr_in address;
bzero( &address, sizeof( address ) );
address.sin_family = AF_INET;
inet_pton( AF_INET, ip, &address.sin_addr );
address.sin_port = htons( port );
int sock = socket( PF_INET, SOCK_STREAM, 0 ); //创建socket,使用TCP协议
assert( sock >= 0 );
//命名socket
int ret = bind( sock, ( struct sockaddr* )&address, sizeof( address ) );
assert( ret != -1 );
//监听socket
ret = listen( sock, 5 );
assert( ret != -1 );
struct sockaddr_in client;
socklen_t client_addrlength = sizeof( client );
//接收连接
int connfd = accept( sock, ( struct sockaddr* )&client, &client_addrlength );
if ( connfd < 0 )
{
printf( "errno is: %d\n", errno );
}
else
{
char buffer[ BUF_SIZE ];
memset( buffer, '\0', BUF_SIZE );
ret = recv( connfd, buffer, BUF_SIZE-1, 0 );
printf( "got %d bytes of normal data '%s'\n", ret, buffer );
close( connfd ); //关闭连接socket
}
close( sock ); //关闭监听socket
return 0;
}