网络编程基础一、Socket概述 Socket接口是TCP/IP网络的API,它定义了许多函数或例程,程序员可以用它们来开发TCP/IP网络上的应 用程序。常见的Socket有以下三种类型。 流式Socket(SOCK_STREAM): 提供可靠的、面向连接的通信流。使用TCP协议 数据报Socket(SOCK_DGRAM): 定义了一种无连接的服务,使用UDP协议,不保证传输的可靠性和数据的原 始顺序 原始Socket:允许对底层协议进行直接访问,如IP,它的功能强大,用户可以通过该Socket开发自己的 协议二、基本数据结构和函数1、网络地址(1)结构体sockaddr struct sockaddr{ unsigned short sa_family;//地址族,AF_xxx char sa_data[14];//14字节的协议地址 }; sa_family: 一般为AF_INET,代表Internet(TCP/IP)地址族的IPV4协议 sa_data: 包含了一些远程计算机的IP地址、端口号和套接字的数目,这些数据是混杂在一起的 (2)结构体sockaddr_in struct sockaddr_in{ short int sin_family; //地址族 unsigned short int sin_port;//端口号 struct in_addr sin_addr; //IP号 unsigned char sin_zero[8]; }; sin_zero:用来将结构体struct sockaddr_in结构填充到与struct sockaddr同样的长度, 可以用bzero()或memset()函数将其置为零 指向sockaddr_in的指针与指向sockaddr的指针可以相互转换。2、IP地址转换 网络上的IP都是点分十进制的格式,如“192.168.0.1”,struct in_addr结构中用的是32位的IP, 如IP(C0A80001)是“192.168.0.1”,俩地址的转换函数: int inet_aton(const char * cp, stuct in_addr * inp)//点分十进制转为32位的IP输入(inp) 返回值:成功——非零值 失败——0 char * inet_ntoa(struct in_addr in)//32位的IP转为点分十进制 返回值:成功——返回指向存储着点分十进制的静态缓冲区的指针 失败——NULL3、IP和域名的转换 将域名转换为IP地址可以使用以下两个函数: struct hostent * gethostbyname(const char * hostname); 功能:将机器名转换为一个结构指针,在这个结构里面储存了域名的信息 返回值:失败时返回NULL,且设置h_errno错误变量,调用h_strerror()可以得到详细的出错信息 struct hostent * gethostbyaddr(const char * addr, int len, int type); len: 字节长度(4) type: 族(AF_INET) 功能:将一个32位的IP地址转换为结构指针 返回值:失败时返回NULL,且设置h_errno错误变量,调用h_strerror()可以得到详细的出错信息 hostent结构体: struct hostent{ char * h_name; //主机的正式名称 char * h_aliases; //主机的别名 int h_addrtype; //主机的地址类型AF_INET int h_length; //主机的地址长度对于IP4是4字节32位 char ** h_addr_list; //主机的IP地址列表 }; #define h_addr h_addr_list[0]//主机的第一个IP地址4、字节顺序转换 计算机数据存储有两种字节优先顺序:高位字节优先和地位字节优先。优先顺序不同会出现数据不一致 的情况,为了统一,linux中有专门的字节转换函数: unsigned long int htonl(unsigned long int hostlong); unsigned short int htons(unisgned short int hostshort); unsigned long int ntohl(unsigned long int netlong); unsigned short int ntohs(unsigned short int netshort); h: host n: network s: short l: long 5、服务信息函数(不常用) 在网络程序中有时需要知道端口、IP和服务信息。可以使用以下函数获得: int getsockname(int sockfd, struct sockaddr * localaddr, int * addrlen); int getpeername(int sockfd, struct sockaddr * peeraddr, int * addrlen); struct servent * getservbyname(const char * servname, const char * protoname); struct servent * getservbyport(int port, const char * protoname); struct servent{ char * s_name; //正式服务名 char ** s_aliases; //别名列表 int s_port; //端口号 char * s_proto; //使用的协议 }; 三、Socket编程基础 示例;(需要输入IP地址)
int main(int argc, char ** argv){ struct sockaddr_in addr; struct hostent * host; char ** alias; if(argc < 2){ fprintf(stderr,"Usage:%s hostname|ip..\n\a",argv[0]); exit(1); } argv++; for(; *argv!=NULL; argv++){ if(inet_aton(* argv, &addr.sin_addr)!=0){//将点分十进制转换为32位的IP存入addr.sin_addr host = gethostbyaddr((char *)&addr.sin_addr, 4, AF_INET); printf("Address information of IP %s\n", * argv); } else{ host = gethostbyname(* argv); printf("Address information of IP %s\n", * argv); } if(host == NULL){ fprintf(stderr, "No address information of %s\n", * argv); continue; } printf("Official host name %s\n",host->h_name);//显示主机名 printf("Name aliases:"); for(alias=host->h_aliases; * alias!=NULL;alias++)//显示所有别名 printf("%s,",*alias); printf("\nIP address:"); for(alias=host->h_addr_list; *alias!=NULL; alias++)//显示IP地址 printf("%s,",inet_ntoa(*(struct in_addr *)(*alias)));//将32位的IP转换为点分十进制 } }
三、TCP通信编程1、创建套接字 int socket(int family, int type, int protocol) family:协议或地址族 type:套接字类型(SOCK_STREAM,SOCK_DGRAM) protocol: 协议号,0指定family和type的默认协议号 返回值:成功——返回一个×××Socket描述符,错误——返回-1并设置error 2、绑定本地地址 int bind(int sockfd, struct sockaddr * sa, int addrlen) sockfd: 套接字描述符 sa: 指向struct sockaddr的指针,包含有关地址的信息(名称、端口和IP地址...) addrlen: 套接字地址接口的长度,可以设置为sizeof(struct sockaddr) 返回值:成功——返回0, 失败——返回-1并设置error bind函数将Socket与本机上的一个端口相关联,随后即可在该端口监听服务请求。 3、listen()函数 int listen(int sockfd, int backlog) backlog: 未经处理的连接请求队列可以容纳的最大数目 listen()函数将套接字设置为监听模式,以等待连接请求。 4、accept()函数 int accept(int sockfd, struct sockaddr * addr, int * addrlen) addr: 一般是一个指向struct sockaddr_in结构的指针,将在accept函数调用返回后填入远程连接过来的 计算机的信息,如远程计算机的IP地址和端口 addrlen:表示参数addr所占的内存区大小,在accept函数调用返回后填入返回的addr结构体的大小 accept()函数用于响应连接请求,建立连接并产生一个新的socket描述符来描述该连接,该连接用来与特定 的客户端交换信息,默认为阻塞函数。 5、connect()函数 int connect(int sockfd, struct sockaddr * serv_addr, int addrlen) serv_addr:存储远程计算机的IP地址和端口信息的sockadddr结构 addrlen:是serv_addr结构体所占的内存大小 返回值:成功——返回0,错误——返回-1 connect()调用成功后,就可以使用sockfd作为服务器连接的套接字描述符,使用I/O函数进行数据传输了。 6、数据通信(1)send()和recv()函数(最基本的、通过连接的套接字流进行通信的函数) int send(int sockdfd, const void * msg, int len, int flags); 返回值:返回实际发送数据的长度,错误——返回-1 int recv(int sockfd, void * buf, int len, unsigned int flags); 返回值:返回真正收到数据的长度,错误——返回-1 sockfd: 与远程程序连接的套接字描述符 msg: 指向想发送或存储信息的地址 len: 信息的长度 flags:发送或接收标记,一般设为0 (2)sendto()和recvfrom()函数(进行无连接的UDP通信时使用) int sendto(int sockfd, const void * msg, int len, unsigned int flags, const struct sockaddr * to, int tolen); int recvfrom(int sockfd, void * buf, int len, unsigned int flags, truct sockaddr * from, int fromlen);(4)使用close()函数关闭当前的连接 int close(int sockfd)