linux网络编程学习1
准备好好学习网络编程,这将是一个稍微漫长的过程(因为还有好多别的事情要做)。
目前学习思路是:例子—>书—>总结—>书
今天的例子是:建立一个服务器与客户端的连接,能接收和发送消息就行了。(read和write的部分我还没看,以后再补)
其实,有过java网络编程经验的童鞋,再看这些内容,会发现很多地方都是通的。
1、代码先行
第一个文件:server.c
#include <stdio.h> #include <unistd.h> #include <ctype.h> #include <sys/socket.h> #include <arpa/inet.h> #include <strings.h> #include <stdlib.h> #define MAX_LINE 100 #define INET_ADDRDTRLEN 16 /** 一个转换函数,无关紧要 */ void my_fun(char *p) { if(p == NULL) return; for(; *p!=‘\0‘;p++) if(*p>=‘A‘ && *p <=‘Z‘) *p = ‘a‘+ (*p - ‘A‘); } int main(void) { struct sockaddr_in sin;//服务端地址结构 struct sockaddr_in cin;//客户端地址结构 int l_fd,c_fd;//l_fd:listener的套接字,c_fd:连接的套接字 socklen_t len; char buf[MAX_LINE];//存储从客户端发送的内容 char addr_p[INET_ADDRDTRLEN];//存储客户端的ip int port = 8000;//服务器的端口号 int n;//读写字节数 bzero(&sin,sizeof(sin)); sin.sin_family = AF_INET;//ipv4 sin.sin_addr.s_addr = INADDR_ANY;//接受任何地址 sin.sin_port = htons(port);//服务器监听的端口号 l_fd = socket(AF_INET,SOCK_STREAM,0);//建立监听套接字 int b = bind(l_fd,(struct sockaddr *)&sin,sizeof(sin));//将地址和套接字绑定 if(0!=b) { printf("failed to bind\n"); exit(1); } listen(l_fd,10);//开始监听 printf("wait..\n"); while(1) { c_fd = accept(l_fd,(struct sockaddr *)&cin,&len);/*如果没有客户端的请求,就会阻塞,如果有的话,就可以开始通信了,并返回连接套接字*/ n = read(c_fd, buf, MAX_LINE); inet_ntop(AF_INET, &cin.sin_addr, addr_p, sizeof(addr_p));/*将客户端地址(cin.sin_addr)转成十进制的字符串(addr_p)*/ printf("client ip is %s,port is %d\n", addr_p, ntohs(cin.sin_port));/*ntohs将二进制的sin_port转为十进制*/ printf("content is: %s\n", buf); my_fun(buf); write(c_fd, buf, n);//写回 close(c_fd);//关闭套接字 } if(close(l_fd)==-1) { perror("fail to close\n"); exit(1); } return 0; }
第二个文件:client.c
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <strings.h> #include <stdlib.h> #define MAX_LINE 100 int main(int argc, char * argv[]) { struct sockaddr_in sin;//linux中的网络通信地址结构 char buff[MAX_LINE]; int sfd;//套接字描述符 int port = 8000;//服务端端口 char *str = "test string"; if(argc > 1) { str = argv[1]; } bzero(&sin,sizeof(sin));//清空 sin.sin_family = AF_INET;//地址族设为ipv4 inet_pton(AF_INET,"127.0.0.1",&sin.sin_addr);//将十进制的ip地址转为二进制 sin.sin_port = htons(port);//将port转为网络字节序 sfd = socket(AF_INET,SOCK_STREAM, 0);//创建套接字,并返回套接字描述符 int c = connect(sfd,(struct sockaddr *)&sin,sizeof(sin));//建立连接,传入套接字描述符,地址,地址长度 if(0!=c) { printf("failed to connect!\n"); exit(1); } int len = strlen(str); write(sfd,str,len+1); read(sfd,buff,MAX_LINE); printf("recive from server: %s\n", buff); close(sfd);//关闭socket return 0; }
第三个文件makefile
all:server client server:server.c gcc -o server server.c client:client.c gcc -o client client.c clean: rm server client clean~: rm *~
2、运行截图
编译
运行
3、分析过程
服务端:
(1)利用sockaddr_in设置监听地址(地址+端口)
(2)利用socket创建一个套接字
(3)利用bind将套接字(2)与监听地址(1)绑定起来
(4)利用listen开始监听
(5)利用accept()进行接收连接(此时会生成一个新的套接字,与监听套接字不同)
(6)进行read和write操作
客户端:
(1)利用sockaddr_in设置服务器地址
(2)利用socket创建一个套接字
(3)利用connect连接服务器,(2)中返回的套接字就是这个标识
4、函数API
4.1 基础
(1)字节转换
在网络环境中,进程之间的通信时要跨越主机的,因此就会带来字节序不统一的问题。为了解决这个问题,网络协议提供了一种字节序,传输的时候需要先进行转换。
(网络字节序实际上是大端存储,理论上如果你的机器是大端存储的,那么可以不转换,但是一般会考虑到程序移植,此时就会出问题了,因此建议在传输前都要转换一下)
头文件:#include <arpa/inet.h>
//net to host unit32_t ntohl(unit32_t netint32); unit16_t ntohs(unit16_t netint16); //host to net unit32_t htonl(unit32_t hostint32); unit16_t htons(unit16_t hostint16);
(2)地址
头文件:#include <netinet/in.h>
/*表示一个ipv4地址,二进制的*/ struct in_addr { in_addr_t s_addr;//无符号整型 }; /*表示一个地址*/ struct sockaddr_in { sa_family_t sin_family; /*16位地址协议族*/ int port_t sin_port; /*16位的端口号*/ struct in_addr sin_addr; /*32位的ip地址*/ unsigned char sin_zero[8]; /*填充区:8字节*/ }; /*表示一个地址,常用作参数*/ struct sockaddr { sa_family_t sin_family; /*16位地址协议族*/ char sa_data[14]; /*填充区:14字节*/ };
(3)地址转换(十进制与二进制)
头文件:#include <netinet/in.h>
/* ntop & pton ntop 二进制转十进制 pton 十进制转二进制 */ const char* inet_ntop(int domain, const void* restrict_addr, char * restrict_str, socketlen_t size); int inet_pton(int domain, const char* restrict_str, void* restrict addr);
(4)主机信息的获取
头文件:#include <netdb.h>
struct hostent { char * h_name;/*主机名,每个主机只有一个*/ char ** h_aliases;/*主机别名列表*/ int h_addrtype;/*ip地址类型:ipv4 or ipv6*/ int h_length;/*ip地址长度,如果是ipv4则为32位*/ char **h_addr_list;/*ip地址列表,h_addr_list[0]为主机的ip地址*/ };
(5)地址映射(DNS)
头文件:#include <sys/socket.h> #include <netdb.h>
/* hostname:一个主机名或者地址串(IPv4的点分十进制串或者IPv6的16进制串) service:服务名可以是十进制的端口号,也可以是已定义的服务名称,如ftp、http等 hints:可以是一个空指针,也可以是一个指向某个addrinfo结构体的指针,调用者在这个结构中填入关于期望返回的信息类型的暗示。 举例来说:如果指定的服务既支持TCP也支持UDP,那么调用者可以把hints结构中的ai_socktype成员设置成SOCK_DGRAM使得返回的仅仅是适用于数据报套接口的信息。 result:本函数通过result指针参数返回一个指向addrinfo结构体链表的指针。 返回值:0 成功,非0 出错 关于这个函数的一个讲解:http://www.cnblogs.com/cxz2009/archive/2010/11/19/1881693.html */ int getaddrinfo( const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **result );
4.2 socket初识
(1)建立、销毁套接字描述符
头文件:#include <sys/socket.h>
/* domain:套接字域 type:套接字类型 protocol:与特定的地址家族相关的协议,如果指定为0,那么系统就会根据地址格式和套接类别,自动选择一个合适的协议.这是推荐使用的一种选择协议的方法. */ int socket(int domain, int type, int protocol);
如果返回-1则创建套接字失败,否则返回套接字。
socket函数就像open函数一样,得到套接字后,就可以像操作文件一样操作套接字了,一般来说使用完了之后,要用close()关闭掉。
表1 套接字域描述
域名 | 域的作用 |
AF_INET | ipv4 |
AF_INET6 | ipv6 |
AF_UNIX | 用于非网络环境的进程通信 |
AF_UNSPIC | 未指定域 |
表2 套接字类型描述与该套接字对应的默认协议
套接字类型 | 类型描述 | 默认协议 |
SOCK_DGRAM | 长度固定的,无连接报文 | UDP |
SOCK_RAW | 原始套接字 | 绕过协议 |
SOCK_SEQPACKET | 长度固定的,面向连接报文 | SCTP |
SOCK_STREAM | 有序的,面向连接的字节流 | TCP |
(2)地址绑定
头文件:#include <sys/socket.h>
如果只有一个套接字,我们还是什么都做不了,因此需要将套接字和地址绑定起来才可以进行网络通信。
/* sockfd: 监听套接字描述符 addr:将要绑定的地址(是sockaddr类型的) len:地址的长度 */ int bind(int sockfd, const struct sockaddr * addr, socklen_t len);
(3)建立连接
头文件:#include <sys/socket.h>
客户端:
/* sockfd:套接字描述符 addr:一般而言是服务器的地址 len:地址的长度 返回值:0 成功 非0 失败,成功后对sockfd的操作,就想到与跟server的通信了 */ int connect(int sockfd, const struct sockaddr* addr, socklen_t len);
服务端:
/* sockfd:套接字描述符,监听的套接字描述符 backlog:最多可以排队等待连接的请求数量 返回值:0 监听成功 非0 失败 */ int listen(int sockfd, int backlog);
/* sockfd:套接字描述符,一般是监听的套接字描述符 addr:地址结构,一般是客户端的地址(可以填NULL) len:地址长度(可以填NULL) 返回:成功返回新的套接字文件描述符 失败则返回-1,之后对套接字的操作,就相当于对client端的通信了 */ int accept(int sockfd, struct sockaddr* addr, socklen_t len);
总结:
通过这个程序,算是了解一点点网络编程的知识了,等把这些例子都看完了,再去看APUE应该会好受一点了吧。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。