全部博文(842)
分类: LINUX
2012-05-17 16:39:58
在TCP/IP网络应用中,通信的两个进程间相互作用的主要模式是客户/服务器模式(Client/Server model),即客户向服务器发出服务请求,服务器接收到请求后,提供相应的服务。
服务器端:
首先服务器方要先启动,并根据请求提供相应服务:
1. 打开一通信通道并告知本地主机,它愿意在某一公认地址上(周知口,如FTP为21)接收客户请求;
2. 等待客户请求到达该端口;
3. 接收到重复服务请求,处理该请求并发送应答信号。接收到并发服务请求,要激活一新进程来处理这个客户请求(如UNIX系统中用fork、exec)。新进程处理此客户请求,并不需要对其它请求作出应答。服务完成后,关闭此新进程与客户的通信链路,并终止。
4. 返回第二步,等待另一客户请求。
5. 关闭服务器
客户端:
1. 打开一通信通道,并连接到服务器所在主机的特定端口;
2. 向服务器发服务请求报文,等待并接收应答;继续提出请求......
3. 请求结束后关闭通信通道并终止。
、创建套接字──socket()
应用程序在使用套接字前,首先必须拥有一个套接字,系统调用socket()向应用程序提供创建套接字的手段,其调用格式如下:
#include
#include
int socket(int domain,int type,int protocol);
第一个参数domain设置为“AF_INET”。
第二个参数是套接口的类型:SOCK_STREAM或SOCK_DGRAM。第三个参数设置为0。
系统调用socket()只返回一个套接字描述符,如果出错,则返回-1。
应用示例:
TCP 方式:sockfd = socket(AF_INET,SOCK_STREAM,0);
UDP 方式:sockfd =socket(AF_INET, SOCK_DGRAM,0);
举例:
#include
#include
main() { int sock; sock=socket(AF_INET,SOCK_STREAM,0); if(sock<0) { printf("socket is error"); return -1; } …… } |
、关闭套接字──close() 和shutdown()
使用close()调用关闭连接的套接字文件描述符:
close(sockfd);
这样就不能再对此套接字做任何的读写操作了。
使用系统调用shutdown()部分关闭socket连接,可有更多的控制权。它允许在某一个方向切断通信,或者切断双方的通信:
int shutdown(int sockfd,int how);
第一个参数是你希望切断通信的套接字文件描述符。第二个参数how值如下:
0—不允许接收
1—不允许发送
2—不允许接收和发送(类似于close())
shutdown()如果成功则返回0,如果失败则返回-1。
、TCP方式:
一旦你有了一个套接口以后,下一步就是把套接口绑定到本地计算机的某一个端口上。如果想用listen()来监听某一端口的数据,这是必要一步,但如果只想使用connect()则无需调用bind函数。
下面是系统调用bind()的使用方法:
#include
#include
int bind(int sockfd,struct sockaddr *my_addr,int addrlen);
第一个参数sockfd是由socket()调用返回的套接字文件描述符。
第二个参数my_addr是指向数据结构sockaddr的指针。数据结构sockaddr中包括了端口和IP地址的信息。
第三个参数addrlen可以设置成sizeof(struct sockaddr)。
举例:
#include #include #include #define MYPORT 6666
main() { int sock; struct sockaddr_in my_addr; sock=socket(AF_INET,SOCK_STREAM,0); if(sock<0) { printf("socket is error"); return -1; } my_addr.sin_family=AF_INET;/*hostbyteorder*/ my_addr.sin_port=htons(MYPORT);/*short,network byte order*/ my_addr.sin_addr.s_addr=inet_addr("132.241.5.10"); bzero(&(my_addr.sin_zero),8);/*zero the rest of the struct*/ if(bind(sock,(struct sockaddr*)&my_addr,sizeof(struct sockaddr))<0) { printf("bind error"); return -1; } ... } |
如果出错,bind()也返回-1。
在调用 bind() 的时候,要注意的另一件事情是:不要采用小于 1024的端口号。所有小于1024的端口号都被系统保留!你可以选择从1024 到65535的端口(如果它们没有被别的程序使用的话)。
如果你使用connect()系统调用,那么你不必知道你使用的端口号。当你调用connect()时,它检查套接口是否已经绑定,如果没有,它将会分配一个空闲的端口。
如果希望不连接到远程的主机,也就是说你希望等待一个进入的连接请求,然后再处理它们。这样,你通过首先调用listen(),然后再调用accept()来实现。
系统调用listen()的形式如下:
int listen(int sockfd,int backlog);
第一个参数是系统调用socket()返回的套接口文件描述符。
第二个参数是进入队列中允许的连接的个数。进入的连接请求在使用系统调用accept()应答之前要在进入队列中等待。这个值是队列中最多可以拥有的请求的个数。大多数系统的缺省设置为20。你可以设置为5或者10。当出错时,listen()将会返回-1值。
当然,在使用系统调用listen()之前,我们需要调用bind()绑定到需要的端口,否则系统内核将会让我们监听一个随机的端口。所以,如果你希望监听一个端口,下面是应该使用的系统调用的顺序:
socket();
bind();
listen();
/* accept() 应该在这 */
举例:
#include
#include
#include
#include
#include
#define MYPORT 6666
main() { int sock,new_fd; int sin_size; struct sockaddr_in my_addr; struct sockaddr_in their_addr; sock=socket(AF_INET,SOCK_STREAM,0); if(sock<0) { printf("socket is error"); return -1; } my_addr.sin_family=AF_INET;/*hostbyteorder*/ my_addr.sin_port=htons(MYPORT);/*short,network byte order*/ my_addr.sin_addr.s_addr=INADDR_ANY; bzero(&(my_addr.sin_zero),8);/*zero the rest of the struct*/ if(bind(sock,(struct sockaddr*)&my_addr,sizeof(struct sockaddr))<0) { printf("bind error"); return -1; } if(listen(sock,5)<0) { printf("listen error"); return -1; } …… } |
系统调用accept()比较复杂。在远程的主机可能试图使用connect()连接你使用listen()正在监听的端口。但此连接将会在队列中等待,直到使用accept()处理它。调用accept()之后,将会返回一个全新的套接口文件描述符来处理这个单个的连接。这样,对于同一个连接来说,就有了两个文件描述符。原先的一个文件描述符正在监听你指定的端口,新的文件描述符可以用来调用send()和recv()。
调用的例子如下:
#include
int accept(intsockfd,void*addr,int*addrlen);
第一个参数是正在监听端口的套接字文件描述符。第二个参数addr是指向本地的数据结构sockaddr_in的指针。调用connect()中的信息将存储在这里。通过它你可以了解哪个主机在哪个端口呼叫你。第三个参数同样可以使用sizeof(struct sockaddr_in)来获得。
如果出错,accept()也将返回-1。
举例:
#include #include #include #include #include #define MYPORT 6666
main() { int sock,new_fd; int sin_size; struct sockaddr_in my_addr; struct sockaddr_in their_addr; sock=socket(AF_INET,SOCK_STREAM,0); if(sock<0) { printf("socket is error"); return -1; } my_addr.sin_family=AF_INET;/*hostbyteorder*/ my_addr.sin_port=htons(MYPORT);/*short,network byte order*/ my_addr.sin_addr.s_addr=INADDR_ANY; bzero(&(my_addr.sin_zero),8);/*zero the rest of the struct*/ if(bind(sock,(struct sockaddr*)&my_addr,sizeof(struct sockaddr))<0) { printf("bind error"); return -1; } if(listen(sock,5)<0) { printf("listen error"); return -1; } sin_size=sizeof(struct sockaddr_in); new_fd=accept(sock,&their_addr,&sin_size); if(new_fd<0) { printf("accept error"); return -1; } …… } |
系统调用connect()的用法如下:
#include
#include
int connect(int sockfd,struct sockaddr* serv_addr,int addrlen);
第一个参数还是套接口文件描述符,它是由系统调用socket()返回的。
第二个参数是serv_addr,指向数据结构sockaddr的指针,其中包括目的端口和IP地址。
第三个参数可以使用sizeof(struct sockaddr)获得。
举例:
#include
#include
#include
#include
#include
#define DEST_PORT 6666 #define DEST_IP "127.0.0.1"
int main(int argc,char argv[]) { int sockfd; struct sockaddr_in dest_addr; sockfd=socket(AF_INET,SOCK_STREAM,0); if(sockfd<0) { printf("socket error"); return -1; } dest_addr.sin_family=AF_INET; dest_addr.sin_port=htons(DEST_PORT); dest_addr.sin_addr.s_addr=inet_addr(DEST_IP); bzero(&(dest_addr.sin_zero),8); if(connect(sockfd,(struct sockaddr*)&dest_addr,sizeof(struct sockaddr))<0) { printf("connect error"); return -1; } …… } |
同样,如果出错,connect()将会返回-1,可能是连接超时或无法访问。
──send()与recv()下面,我们将可以使用新创建的套接字文件描述符new_fd来调用send()和recv()。
系统调用send()的用法如下:
int send(int sockfd,const void* msg,int len,int flags);
第一个参数是你希望给发送数据的套接字文件描述符。它可以是你通过socket()系统调用返回的,也可以是通过accept()系统调用得到的。
第二个参数是指向你希望发送的数据的指针。
第三个参数是数据的字节长度。第四个参数标志设置为0。
举例:
#include
#include
#include
#include
#include
#define DEST_PORT 6666 #define DEST_IP "127.0.0.1"
int main(int argc,char argv[]) { int sockfd; struct sockaddr_in dest_addr; char *msg="Hello World!"; int len,bytes_sent; sockfd=socket(AF_INET,SOCK_STREAM,0); if(sockfd<0) { printf("socket error"); return -1; } dest_addr.sin_family=AF_INET; dest_addr.sin_port=htons(DEST_PORT); dest_addr.sin_addr.s_addr=inet_addr(DEST_IP); bzero(&(dest_addr.sin_zero),8); if(connect(sockfd,(struct sockaddr*)&dest_addr,sizeof(struct sockaddr))<0) { printf("connect error"); return -1; } len=strlen(msg); bytes_sent=send(sockfd,msg,len,0); …… } |
系统调用send()返回实际发送的字节数,这可能比你实际想要发送的字节数少。如果返回的字节数比要发送的字节数少,你在以后必须发送剩下的数据。当send()出错时,将返回-1。
系统调用recv()的使用方法和send()类似:
int recv(int sockfd,void* buf,int len,unsigned int flags);
第一个参数是要读取的套接口文件描述符。
第二个参数是保存读入信息的地址。
第三个参数是缓冲区的最大长度。第四个参数设置为0。
系统调用recv()返回实际读取到缓冲区的字节数,如果出错则返回-1。
举例:
#include
#include
#include
#include
#include
#define MYPORT 6666
main() { int sock,new_fd; int sin_size; struct sockaddr_in my_addr; struct sockaddr_in their_addr; int len,recv_size; char buf[255]; sock=socket(AF_INET,SOCK_STREAM,0); if(sock<0) { printf("socket is error"); return -1; } my_addr.sin_family=AF_INET;/*hostbyteorder*/ my_addr.sin_port=htons(MYPORT);/*short,network byte order*/ my_addr.sin_addr.s_addr=INADDR_ANY; bzero(&(my_addr.sin_zero),8);/*zero the rest of the struct*/ if(bind(sock,(struct sockaddr*)&my_addr,sizeof(struct sockaddr))<0) { printf("bind error"); return -1; } if(listen(sock,5)<0) { printf("listen error"); return -1; } printf("listen right\n"); sin_size=sizeof(struct sockaddr_in); new_fd=accept(sock,&their_addr,&sin_size); if(new_fd<0) { printf("accept error"); return -1; } recv_size=recv(new_fd,buf,255,0); …… } |
这样使用上面的系统调用,就可以通过数据流套接口来发送和接受信息。
、UDP方式 sendto() 和 recvfrom()
1. sendto()
既然数据报套接字不是连接到远程主机的,在发送一个包之前需要目标地址,系统调用sendto()的用法如下:
int sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int tolen);
除了两个信息外,其余的和函数 send() 是一样 的。 to 是个指向数据结构 struct sockaddr 的指针,它包含了目的地的 IP 地址和端口信息。tolen 可以简单地设置为sizeof(struct sockaddr)。和函数 send() 类似,sendto()返回实际发送的字节数(它也可能小于你想要发送的字节数),或者在错误的时候返回 -1。
举例:
#include
#include
#include
#include
#include
#include
#include
#include
#define DEST_PORT 6666 #define DEST_IP "127.0.0.1"
int main(int argc,char argv[]) { int sockfd; int write_buf,len; char *buf="Hello World!"; struct sockaddr_in dest_addr; dest_addr.sin_family=AF_INET; dest_addr.sin_port=htons(DEST_PORT); dest_addr.sin_addr.s_addr=inet_addr(DEST_IP); sockfd=socket(AF_INET,SOCK_DGRAM,0); if(sockfd<0) { printf("socket error\n"); return -1; } len=strlen(buf); write_buf=sendto(sockfd,buf,len,0,(struct sockaddr *)&dest_addr,sizeof(struct sockaddr)); if(write_buf<0) { printf("send error\n"); close(sockfd); return -1; } …… } |
2. recvfrom()
recvfrom() 的定义是这样的:
int recvfrom(int sockfd, void *buf, int len, unsigned int flags, struct sockaddr *from, int *fromlen);
同理,除了两个增加的参数外,这个函数和 recv() 也是一样的。from 是一个指向局部数据结构 struct sockaddr 的指针,它的内容是源机器的 IP 地址和端口信息。fromlen 是个 int 型的局部指针,它的初始值为 sizeof(struct sockaddr)。函数调用返回后,fromlen 保存着实际储存在 from 中的地址的长度。
recvfrom() 返回收到的字节长度,或者在发生错误后返回 -1。
举例:
#include #include #include #include #include #include
#include #include #define MYPORT 6666
int main(int argc,char argv[]) { int sockfd; int recv_buf,len; char buf[256]; struct sockaddr_in my_addr; struct sockaddr_in their_addr; my_addr.sin_family=AF_INET; my_addr.sin_port=htons(MYPORT); my_addr.sin_addr.s_addr=INADDR_ANY; bzero(&(my_addr.sin_zero),8); sockfd=socket(AF_INET,SOCK_DGRAM,0); if(sockfd<0) { printf("socket error\n"); return -1; } if(bind(sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))<0) { printf("bind error\n"); close(sockfd); return -1; }
int fromlen=sizeof(struct sockaddr); recv_buf=recvfrom(sockfd,buf,256,0,(struct sockaddr *)&their_addr,&fromlen); if(recv_buf<0) { printf("send error\n"); close(sockfd); return -1; } …… } |
与TCP 方式的区别:
需要指定发送/接收数据的对方(第五个参数to/from)函数返回实际发送/接收的字节数,返回-1 表示出错。
函数缺省是阻塞函数,直到发送/接收完毕或出错