12:基于TCP的通信程序的设计
12.1 套接字的类型
1.流套接字SOCK_STREAM
是双向的,可靠的,顺序的,不重复的,面向连接的,tcp协议
2.数据报套接字SOCK_DGRAM
无连接,独立的,无序的,不保证可靠性,udp协议
3.原始套接字SOCK_RAW
用于底层开发,一般不用
12.2 socket的创建
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
函数socket创建一个socket描述符,domain指定发送通信的域,即套接字的
协议族,每个协议族对应一个以“AF_"开头的宏常量:
AF_UNIX 本地主机通信,功能与IPC对象类似
AF_INET internet地址IPV4协议
(在实践中只使用第二种)
参数type指定了通信的类型:
SOCK_STREAM, SOCK_DGRAM, SOCK_RAW
protocal指定了该套接字描述符上的一个特殊协议,比如TCP,UDP一般设置为0
调用成功返回套接字的描述符,否则-1,并置errno
例1:创建AF_INET协议族上的流套接字描述符
socket(AF_INET, SOCK_STREAM, 0);
因为流套接字底层通信协议是TCP,所以以上语句等同于:
socket(AF_INET,SOCK_STREAM,TCP);
12.3 socket的命名--系统调用bind
#include <sys/types.h>
#include <sys/socket.h>
int bind(int s, const struct sockaddr *name, int namelen);
参数s指定了套接字描述符,这个值由函数socket返回,指针name指向通用套接字
的协议地址结构,包括协议,地址,和端口信息等,namelen指定该地址结构的长
度,一般用“sizeof(sockaddr_in)",调用成功时返回0,或者-1,并置errno
struct sockaddr
{
u_short sa_family; 协议族
char sa_data[14]; 最多14字节的协议地址
};
sa_data描述协议的地址,比如AF_INET使用sockaddr_in描述套接字的地址信息
struct sockaddr_in
{
short sin_family; 16位的地址协议族(AF_INET)
u_short sin_port; 16位的端口地址
struct in_addr sin_addr; 32位的IP地址
char sin_zero[8]; 预留
}
struct in_addr
{
u_long s_addr;
};
成员sin_family的取值含义与sa_famliy相同,成员sin_port指定了创建的端口号
sin_addr以无符号长整型的方式记载IP地址,
1.ip地址转换
unsigned long inet_addr(char *ptr);
int inet_aton(char *ptr,struct in_addr *addrptr);
char *inet_ntoa(struct in_addr inaddr);
2.字节顺序转换
#include <sys/types.h>
#include <netinet/in.h>
u_long htonl(u_long hostlong);
u_short htons(u_short hostshort);
u_long ntohl(u_long netlong);
u_short ntohs(u_short netshort);
htons:将16位的整数由主机字节顺序转换成网络字节顺序
htonl:将32位的整数由主机字节顺序转换成网络字节顺序
ntohl:将32位的整数由网络字节顺序转换成主机字节顺序
ntohs:将16位的整数由网络字节顺序转换成主机字节顺序
例1:将套接字端口1000由主机字节顺序转换为网络字节顺序
htonl(1000);
例2:命名套接字描述符s的协议为AF_INET,地址为“127.0.0.1”,端口为1000
struct sockaddr_in sockaddr1;
memset(&sockaddr1, 0, sizeof(sockaddr1));
sockaddr1.sin_family = AF_INET;
sockaddr1.sin_addr.s_addr = inet_addr("127.0.0.1");
sockaddr1.sin_port = htons(100000);
bind(s, (struct sockaddr *)&sockaddr1,sizeof(sockaddr));
12.4 socket的侦听--系统调用listen
#include <sys/types.h>
#include <sys/socket.h>
int listen(int s,int backlog);
s是创建的套接字,backlog确定了套接字s接收链接的最大数目
成功返回0,否则返回-1,
12.5 socket的链接处理--系统调用accept
#include <sys/types.h>
#include <sys/socket.h>
int accept(int s, struct sockaddr *addr, int *addrlen);
成功返回一个与s相同的新的套接字描述符,addr回传连接成功的客户端地址结构
12.6 socket的关闭
#include <sys/socket.h>
int shutdown(int s, int how);
12.7 socket的连接--系统调用connect
#include <sys/types.h>
#include <sys/socket.h>
int connect(int s,const struct sockaddr *name,int namelen);
s是本地套接字描述符,name指定了对方的套接字地址结构,成功返回0
例1:客户端套接字s向服务器“xxx.xxx.xxx.xxx"提交telnet连接申请(23端口)
struct sockaddr_in sockaddr1;
memset(&sockaddr1,0,sizeof(sockaddr1));
sockaddr1.sin_family = AF_INET;
sockaddr1.sin_addr.s_addr = inet_addr("xxx.xxx.xxx.xxx.);
sockaddr1.sin_port = htons(23);
connect(s, (struct sockaddr *)&sockaddr1,sizeof(sockaddr1));
12.8 TCP数据的发送--系统调用send
#include <sys/types.h>
#include <sys/socket.h>
int send(int s, const void *msg, int len, int flags);
s是远程地址连接的套接字描述符,msg为待发送的数据消息
flags是发送标志:
MSG_OOB 发送外带数据
MSG_DONTROUTE 通知内核远程IP就在本地局域网内,消息中不加入路由信息
例1:通过套接字s向远程地址发送字符串“hello world“
send(s,"hello world",strlen("hello world!"),0);
12.9 TCP数据的接收--系统调用recv
#include <sys/types.h>
#include <sys/socket.h>
int recv(int s, void *buf, int len, int flags);
s是远程地址连接的套接字,buf是存放数据的缓冲区
flags是接收标志:
MSG_OOB 接收外带数据
MSG_PEEK 只接收而不从缓冲区删除数据
MSG_WAITALL 阻塞直到读取len字节数据为止
成功返回接收的数据长度,没有接收到数据或者已经关闭返回0,否则-1
12.10 socket的域名地址
域名系统DNS可以实现从域名到IP的转换,以下函数借助域名系统实现此功能
#include <netdb.h>
extern int h_errno;
struct hostent *gethostbyname(const char *name);
struct hostent *gethostbyaddr(const char *addr, int len, int type);
void herror(const char *string);
函数gethostbyname从域名服务器中将字符串形式的域名name转化为网络字节顺序的
ip地址,如果name就是字符串形式的ip地址,函数直接返回
hostent存储主机地址信息:
struct hostent
{
char *h_name; 主机的官方名称
char **h_aliases; 主机的别名
int h_addrtype; 地址的类型一般是AF_INET
int h_length; 地址占用的字节长度
char **h_addr_list; 主机的网络字节地址列表
}
gethostbyaddr功能相反,type一般为AF_INET
12.11 socket的端口
以下函数可以显示系统后台进程与其占用的端口之间的关系
#include <netdb.h>
struct servent *getservbyname(const char *name, const char *proto);
struct servent *getservbyport(int port, const char *proto);
函数getservbyname根据服务名称name获取服务的详细信息,getservbyport根据
服务器端口号port获取服务的信息,proto是该服务使用的协议名称
struct servent
{
char *s_name; 服务器官方名称
char **s_aliases; 服务器别名
int s_port; 服务器端口号
char *s_proto; 使用的协议
};
#include <stdio.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main(int argc,char *argv[])
{
struct servent *serv;
if (argc != 2)
return 1;
if ((serv = getservbyname(argv[1],"tcp")) == NULL)
{
herror("getservbyname");
return 2;
}
printf("Serv name:%s\n",serv->s_name);
printf("Serv port:%d\n",ntohs(serv->s_port));
return 0;
}
12.12 socket的协议地址
以下函数获取套接字双方的协议地址信息
int getsockname(int s, struct sockaddr *name, int *namelen);
int getpeername(int s, struct sockaddr *name, int *namelen);
函数1获取本地s的sockaddr信息,函数2获取与s相连的对方的sockaddr信息
12.13 套接字选项
#include <sys/types.h>
#include <sys/socket.h>
int getsockopt(int s,int level,int optname,void *optval,int *optlen);
int setsockopt(int s,int level,int optname,const void *optval,int optlen);
12.1 套接字的类型
1.流套接字SOCK_STREAM
是双向的,可靠的,顺序的,不重复的,面向连接的,tcp协议
2.数据报套接字SOCK_DGRAM
无连接,独立的,无序的,不保证可靠性,udp协议
3.原始套接字SOCK_RAW
用于底层开发,一般不用
12.2 socket的创建
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
函数socket创建一个socket描述符,domain指定发送通信的域,即套接字的
协议族,每个协议族对应一个以“AF_"开头的宏常量:
AF_UNIX 本地主机通信,功能与IPC对象类似
AF_INET internet地址IPV4协议
(在实践中只使用第二种)
参数type指定了通信的类型:
SOCK_STREAM, SOCK_DGRAM, SOCK_RAW
protocal指定了该套接字描述符上的一个特殊协议,比如TCP,UDP一般设置为0
调用成功返回套接字的描述符,否则-1,并置errno
例1:创建AF_INET协议族上的流套接字描述符
socket(AF_INET, SOCK_STREAM, 0);
因为流套接字底层通信协议是TCP,所以以上语句等同于:
socket(AF_INET,SOCK_STREAM,TCP);
12.3 socket的命名--系统调用bind
#include <sys/types.h>
#include <sys/socket.h>
int bind(int s, const struct sockaddr *name, int namelen);
参数s指定了套接字描述符,这个值由函数socket返回,指针name指向通用套接字
的协议地址结构,包括协议,地址,和端口信息等,namelen指定该地址结构的长
度,一般用“sizeof(sockaddr_in)",调用成功时返回0,或者-1,并置errno
struct sockaddr
{
u_short sa_family; 协议族
char sa_data[14]; 最多14字节的协议地址
};
sa_data描述协议的地址,比如AF_INET使用sockaddr_in描述套接字的地址信息
struct sockaddr_in
{
short sin_family; 16位的地址协议族(AF_INET)
u_short sin_port; 16位的端口地址
struct in_addr sin_addr; 32位的IP地址
char sin_zero[8]; 预留
}
struct in_addr
{
u_long s_addr;
};
成员sin_family的取值含义与sa_famliy相同,成员sin_port指定了创建的端口号
sin_addr以无符号长整型的方式记载IP地址,
1.ip地址转换
unsigned long inet_addr(char *ptr);
int inet_aton(char *ptr,struct in_addr *addrptr);
char *inet_ntoa(struct in_addr inaddr);
2.字节顺序转换
#include <sys/types.h>
#include <netinet/in.h>
u_long htonl(u_long hostlong);
u_short htons(u_short hostshort);
u_long ntohl(u_long netlong);
u_short ntohs(u_short netshort);
htons:将16位的整数由主机字节顺序转换成网络字节顺序
htonl:将32位的整数由主机字节顺序转换成网络字节顺序
ntohl:将32位的整数由网络字节顺序转换成主机字节顺序
ntohs:将16位的整数由网络字节顺序转换成主机字节顺序
例1:将套接字端口1000由主机字节顺序转换为网络字节顺序
htonl(1000);
例2:命名套接字描述符s的协议为AF_INET,地址为“127.0.0.1”,端口为1000
struct sockaddr_in sockaddr1;
memset(&sockaddr1, 0, sizeof(sockaddr1));
sockaddr1.sin_family = AF_INET;
sockaddr1.sin_addr.s_addr = inet_addr("127.0.0.1");
sockaddr1.sin_port = htons(100000);
bind(s, (struct sockaddr *)&sockaddr1,sizeof(sockaddr));
12.4 socket的侦听--系统调用listen
#include <sys/types.h>
#include <sys/socket.h>
int listen(int s,int backlog);
s是创建的套接字,backlog确定了套接字s接收链接的最大数目
成功返回0,否则返回-1,
12.5 socket的链接处理--系统调用accept
#include <sys/types.h>
#include <sys/socket.h>
int accept(int s, struct sockaddr *addr, int *addrlen);
成功返回一个与s相同的新的套接字描述符,addr回传连接成功的客户端地址结构
12.6 socket的关闭
#include <sys/socket.h>
int shutdown(int s, int how);
12.7 socket的连接--系统调用connect
#include <sys/types.h>
#include <sys/socket.h>
int connect(int s,const struct sockaddr *name,int namelen);
s是本地套接字描述符,name指定了对方的套接字地址结构,成功返回0
例1:客户端套接字s向服务器“xxx.xxx.xxx.xxx"提交telnet连接申请(23端口)
struct sockaddr_in sockaddr1;
memset(&sockaddr1,0,sizeof(sockaddr1));
sockaddr1.sin_family = AF_INET;
sockaddr1.sin_addr.s_addr = inet_addr("xxx.xxx.xxx.xxx.);
sockaddr1.sin_port = htons(23);
connect(s, (struct sockaddr *)&sockaddr1,sizeof(sockaddr1));
12.8 TCP数据的发送--系统调用send
#include <sys/types.h>
#include <sys/socket.h>
int send(int s, const void *msg, int len, int flags);
s是远程地址连接的套接字描述符,msg为待发送的数据消息
flags是发送标志:
MSG_OOB 发送外带数据
MSG_DONTROUTE 通知内核远程IP就在本地局域网内,消息中不加入路由信息
例1:通过套接字s向远程地址发送字符串“hello world“
send(s,"hello world",strlen("hello world!"),0);
12.9 TCP数据的接收--系统调用recv
#include <sys/types.h>
#include <sys/socket.h>
int recv(int s, void *buf, int len, int flags);
s是远程地址连接的套接字,buf是存放数据的缓冲区
flags是接收标志:
MSG_OOB 接收外带数据
MSG_PEEK 只接收而不从缓冲区删除数据
MSG_WAITALL 阻塞直到读取len字节数据为止
成功返回接收的数据长度,没有接收到数据或者已经关闭返回0,否则-1
12.10 socket的域名地址
域名系统DNS可以实现从域名到IP的转换,以下函数借助域名系统实现此功能
#include <netdb.h>
extern int h_errno;
struct hostent *gethostbyname(const char *name);
struct hostent *gethostbyaddr(const char *addr, int len, int type);
void herror(const char *string);
函数gethostbyname从域名服务器中将字符串形式的域名name转化为网络字节顺序的
ip地址,如果name就是字符串形式的ip地址,函数直接返回
hostent存储主机地址信息:
struct hostent
{
char *h_name; 主机的官方名称
char **h_aliases; 主机的别名
int h_addrtype; 地址的类型一般是AF_INET
int h_length; 地址占用的字节长度
char **h_addr_list; 主机的网络字节地址列表
}
gethostbyaddr功能相反,type一般为AF_INET
12.11 socket的端口
以下函数可以显示系统后台进程与其占用的端口之间的关系
#include <netdb.h>
struct servent *getservbyname(const char *name, const char *proto);
struct servent *getservbyport(int port, const char *proto);
函数getservbyname根据服务名称name获取服务的详细信息,getservbyport根据
服务器端口号port获取服务的信息,proto是该服务使用的协议名称
struct servent
{
char *s_name; 服务器官方名称
char **s_aliases; 服务器别名
int s_port; 服务器端口号
char *s_proto; 使用的协议
};
#include <stdio.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main(int argc,char *argv[])
{
struct servent *serv;
if (argc != 2)
return 1;
if ((serv = getservbyname(argv[1],"tcp")) == NULL)
{
herror("getservbyname");
return 2;
}
printf("Serv name:%s\n",serv->s_name);
printf("Serv port:%d\n",ntohs(serv->s_port));
return 0;
}
12.12 socket的协议地址
以下函数获取套接字双方的协议地址信息
int getsockname(int s, struct sockaddr *name, int *namelen);
int getpeername(int s, struct sockaddr *name, int *namelen);
函数1获取本地s的sockaddr信息,函数2获取与s相连的对方的sockaddr信息
12.13 套接字选项
#include <sys/types.h>
#include <sys/socket.h>
int getsockopt(int s,int level,int optname,void *optval,int *optlen);
int setsockopt(int s,int level,int optname,const void *optval,int optlen);