一、套接字数据结构 1、通用套接字地址 struct sockaddr { sa_family_t sa_family; //通信类型,对于IPV4为AF_INET char sa_data[14]; //用来保存IP地址和端口信息,一般不用 } 2、IPV4套接字地址 struct sockaddr_in { unsigned short sin_len; //IPV4地址长度 sa_family_t sin_family; //通信类型 unsigned short int sin_port; //端口号 struct in_addr sin_addr; //IP地址 unsigned char sin_zero[8]; //补充字段 } 其中struct in_addr为: struct in_addr { uint32_t s_addr;//32位IP地址,网络字节顺序 } 3、hostent struct hostent { char* h_name;//主机的正式名 char** h_aliases;//主机的别名 int h_addrtype;//主机的地址类型,IPV4为AF_INET int h_length;//地址长度,IPV4为32 char** h_addr_list;//主机的IP地址列表 } #define h_addr h_addr_list[0]//主机的第一个IP地址POSIX中的数据类型 数据类型 说明 头文件int8_t 带符号的8位整数 <sys/types.h> uint8_t 无符号的8位整数 <sys/types.h>int16_t 带符号的16位整数 <sys/types.h> uint16_t 无符号的16位整数 <sys/types.h>int32_t 带符号的32位整数 <sys/types.h> uint32_t 无符号的32位整数 <sys/types.h> sa_family_t 套接字地址结构的地址族 <sys/socket.h> socklen_t 套接字地址结构的长度,一般为uint32_t <sys/socket.h>int_port_t TCP或者UDP端口号,一般为uint16_t <netinet/in.h> in_addr_t IPV4地址,一般为uint32_t <netinet/in.h>二、基础函数 1、主机字节序和网络字节序 #include<netinet/in.h> uint32_t htonl(uint32_t hostlong) uint16_t htons(uint16_t hostsho以上rt) 以上两个函数返回网络字节序 uint16_t ntohs(uint16_t netshort) 以上两个函数返回主机字节序 2、字节操作函数 #include<string.h> void memset(void* dest,int c,size_t len) void memcpy(void* dest,const void* src,size_t nbytes) int memcmp(const void* ptr1,const void* ptr2,size_t nbytes) 3、整数与IP地址转换 TCP/IP中的IP地址是以''.''隔开的十进制的数,而套接字中用的是32位的网络字节序的二进制数值。 #include<arpa/inet.h> int inet_aton(const char* straddr,struct in_addr* addrptr) 若成功则返回1,否则返回0 参数:straddr为点分十进制字符串,结果保存在addrptr所指的内存中 char* inet_ntoa(struct in_addr inaddr) 若成功则返回点分十进制数串的指针,否则返回NULL 参数:inaddr为32位网络字节的整数,返回点分十进制数串的指针 in_addr_t inet_addr(const char* straddr) 若成功则返回32位的二进制网络字节序的地址,否则返回INADDR_NONE(表示一个不存在的IP地址,其实就是255.255.255.255,就是-1) 参数:straddr为点分十进制字符串,返回为32位的二进制网络字节序的地址 4、域名与IP地址的转换 #include<netdb.h> struct hostent* gethostbyname(const char* hostname) struct hostent* gethostbyaddr(const char* addr,size_t len,int family) 若成功返回hostent结构指针,否则返回空指针,同时设置h_errno,可以调用hstrerror()函数获取h_errno的值 h_errno的取值: HOST_NOT_FOUND 找不到主机 TRY_AGAIN 出错重试 NO_RECOVERY 不可修复性错误 NO_DATA 指定的名字有效,但是没有记录 5、其他常用函数: 若IP地址设为INADDR_ANY则表示本机IP,这时可以在浏览器中输入 http://localhost:端口号/ 作为客户端向服务器发出链接请求 一般出错信息可以用perror来输出 void perror(const char* s) 该函数除了会输出字符串s,还会输出错误原因 三、TCP编程 头文件:#include<sys/socket.h> 服务器流程:socket()→bind()→listen()→accept()→recv()<═>send()→close() 客户端流程:socket()→connect()→send()<═>recv()→close() 1、创建套接字 int socket(int family,int type,int ptotocol) 若成功则返回套接字描述符,否则返回-1 参数:family取值:PF_INET(等价于AF_INET)、PF_INET6(等价于AF_INET6) (PF代表Protocl Family协议族,AF代表Address Family地址族)
type取值:SOCK_STREAM、SOCK_DGRAM、SOCK_RAW ptotocol一般取0 2、绑定端口 int bind(int sockfd,const struct sockaddr* my_addr,socklen_t addrlen) 若成功返回0,否则返回-1 若出错,则可以在errno中捕获: EBADF:参数sockfd不合法 EACCEDD:权限不足 ENOTSOCK:参数sockfd是一个文件描述符,而不是socket 3、等待监听 int listen(int sockfd,int backlog) 若成功则返回0,否则返回-1 参数:sockfd表示要监听的套接字,backlog表示最多同时处理的请求数(若为IPV4则最多为128),若超过这个个数,则客户端会受到ECONNREFUSED错误 若出错,则可以在errno中捕获: EBADF:参数sockfd不合法 EACCEDD:权限不足 EOPNOTSUPP:指定的socket不支持listen 4、接受链接 int accept(int sockfd,struct sockaddr* addr,socketlen_t* addrlen) 若成功则返回新的套接字,否则返回-1 参数:sockfd表示处于监听的套接字,addr表示客户端的信息 accept接受一个链接时会返回一个新的套接字,以后的数据传输就用这个新的套接字处理 若出错,则可以用errno捕获: EBADF:参数sockfd不合法 EFAULT:参数addr指针指向无法存取的空间 ENOTSOCK:参数sockfd是一个文件描述符,而不是socket EOPNOTSUPP:制定的socket不是SOCK_STREAM EPERM:防火墙拒绝这个链接 ENOBUFS:系统缓冲内存不足 ENOMEM:核心内存不足 5、请求链接 int connect(int sockfd,const struct sockaddr* serv_addr,int addrlen) 若成功返回0,否则返回-1 参数:sockfd表示已经建立好的套接字,serv_addr保存服务器信息 若出错,可以在errno中捕获: EBADF:参数sockfd不合法 EFAULT:参数addr指针指向无法存取的空间 ENOTSOCK:参数sockfd是一个文件描述符,而不是socket EISCONN:参数sockfd的套接字已经处于链接状态 ECONNREFUSED:链接请求被拒绝 ETIMEDOUT:超时 ENETUNREACH:无法传送数据包到指定主机 EAFNOSUPPORT:sockaddr结构的sa_family不正确 EALREADY:socket不能阻断,但是以前的链接操作还未完成 6、数据发送和接收 int send(int sockfd,const void* buf,int len,unsigned int flags) int recv(int sockfd,void* buf,int len,unsigned int flags) 若成功则返回已经发送或者接收的字节数,否则返回-1 flags一般置0 若出错,可以在errno中捕获: EBADF:参数sockfd不合法 EFAULT:参数addr指针指向无法存取的空间 ENOTSOCK:参数sockfd是一个文件描述符,而不是socket EINTR:进程被信号中断 EAGAIN:此动作会中断进程,但参数sockfd的套接字不可中断 ENOBUFS:系统的缓冲内存不足 ENOMEM:核心内存不足 EINVAL:函数参数不正确 7、write和read函数 #include<unistd.h> ssize_t write(int fd,const void* buf,size_t count) ssize_t read(int fd,void* buf,size_t count) 若成功返回写入和读取的字节数,否则返回-1 注意:write等价于send,read等价于recv 8、关闭套接字 #include<unistd.h> int close(int fd) 若成功返回0,否则返回-1 服务器: #include<stdio.h> #include<stdlib.h> #include<string.h> #include<sys/socket.h> #include<sys/types.h> #include<netinet/in.h> #include<netdb.h> #include<errno.h> #include<unistd.h> #include<arpa/inet.h> #define BUFSIZE 1024//定义缓冲区的大小 int main(int argc,char* argv[]) { int sockfd,newsockfd; sockaddr_in server_addr;//服务器的套接字地址 sockaddr_in client_addr;//客户端的套接字地址 int port; char buf[BUFSIZE];//发送缓冲区 socklen_t addr_len=sizeof(sockaddr_in); if(argc!=2)//判断命令行参数 { printf("Arguments error! Usage:%s port\n",argv[0]); exit(1); } //获取端口号 if((port=atoi(argv[1]))<0) { printf("Port error! Usage:%s port\n",argv[0]); exit(1); } if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)//创建服务器Socket { printf("Create socket error:%s\n",strerror(errno)); exit(1); } printf("Create server socket success!Socket id:%d\n",sockfd); //初始化服务器套接字地址 memset(&server_addr,0,sizeof(sockaddr_in));//清零 server_addr.sin_family=AF_INET; server_addr.sin_addr.s_addr=htonl(INADDR_ANY); server_addr.sin_port=htons(port); //绑定 if(bind(sockfd,(sockaddr*)(&server_addr),sizeof(sockaddr))==-1) { printf("Bind error:%s\n",strerror(errno)); exit(1); } printf("Bind port success!local port:%d\n",port); //监听 if(listen(sockfd,5)==-1) { printf("Listen error:%s\n",strerror(errno)); exit(1); } printf("Listening...\n"); //服务器阻塞,等待接收连接请求,知道客户端程序发送连接请求 while(1) { //接收请求 if((newsockfd=accept(sockfd,(sockaddr*)(&client_addr),&addr_len))==-1) { printf("Accept error:%s\n",strerror(errno)); exit(1); } //打印客户端IP printf("Server get connection from %s\n",inet_ntoa(client_addr.sin_addr)); //提示用户输入要发送的数据 printf("Connected successful,please input the mesage[<1024 bytes]:\n"); //数据存放在buf缓冲区,若正确则返回地址等于缓冲区地址 if(fgets(buf,sizeof(buf),stdin)!=buf) { printf("fgets error!\n"); exit(1); } //调用write发送数据,等价于send(newsockfd,buf,strlen(buf),0) //用strlen不用sizeof的原因是只发送用户输入的字符串 if(write(newsockfd,buf,strlen(buf))==-1) { printf("Write error:%s\n",strerror(errno)); exit(1); } close(sockfd); return 0; } } 客户端: #include<stdio.h> #include<stdlib.h> #include<string.h> #include<sys/socket.h> #include<sys/types.h> #include<netinet/in.h> #include<netdb.h> #include<errno.h> #include<unistd.h> #define BUFSIZE 1024 int main(int argc,char* argv[]) { int sockfd; char buffer[BUFSIZE]; sockaddr_in server_addr; hostent* hostname; int port,nbytes; if(argc!=3) { printf("Arguments error! Usage:%s hostname prot\n",argv[0]); exit(1); } //获得主机名 if((hostname=gethostbyname(argv[1]))==NULL) { printf("Get hostname error\n"); exit(1); } //获得端口号 if((port=atoi(argv[2]))<0) { printf("Usage:%s hostname port\n",argv[0]); exit(1); } //建立socket if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1) { printf("Create socket error:%s\n",strerror(errno)); exit(1); } printf("Create client socket success! Socket id:%d\n",sockfd); memset(&server_addr,0,sizeof(server_addr)); server_addr.sin_family=AF_INET; server_addr.sin_port=htons(port); server_addr.sin_addr=*((in_addr*)hostname->h_addr); //发起连接请求 if(connect(sockfd,(sockaddr*)(&server_addr),sizeof(sockaddr))==-1) { printf("Connect error:%s\n",strerror(errno)); exit(1); } //连接成功 printf("Connect success!\n"); if((nbytes=read(sockfd,buffer,BUFSIZE))==-1) { printf("Read error:%s\n",strerror(errno)); exit(1); } buffer[nbytes]='\0'; printf("I have received:%s\n",buffer); close(sockfd); return 0; } 执行: ./server 端口号 ./client localhost 端口号 |
|
来自: sky_feiyang > 《网络》