标签:
上图基本展示了TCP客户端与服务器编程的基本的流程。
1、面向连接编程(TCP)
面向连接的网络应用程序开发流程比较固定,需要开发者创建服务器与客户端两个应用程序,通过网络是想进程间的通讯。
● 服务器端流程
1 创建套接字(socket)
2 服务绑定(bind)
3 服务侦听(listen)
4 处理新到连接(accept)
5 数据收发(recv\send)
6 套接字关闭(close)
● 客户端流程
① 客户端套接字创建(socket)
② 发起连接(connect)
③ 数据发收(send\recv)
④ 套接字关闭(close)
下面大致介绍下上述函数的一些基本的用法:
(1)socket函数:
函数原型:
int socket(int family,int type, int protocol);
socket()函数用于根据指定的地址族、数据类型和协议来分配一个套接口的描述字及其所用的资源。如果协议protocol未指定(等于0),则使用缺省的连接方式。 对于使用一给定地址族的某一特定套接口,只支持一种协议。但地址族可设为AF_UNSPEC(未指定),这样的话协议参数就要指定了。协议号特定于进行通讯的“通讯域”。
支持下述类型描述:
SOCK_STREAM 提供有序的、可靠的、双向的和基于连接的字节流,使用带外数据传送机制,为Internet地址族使用TCP。 SOCK_DGRAM 支持无连接的、不可靠的和使用固定大小(通常很小)缓冲区的数据报服务,为Internet地址族使用UDP。 SOCK_STREAM类型的套接口为全双向的字节流。对于流类套接口,在接收或发送数据前必需处于已连接状态。用connect()调用建立与另一套接口的连接,连接成功后,即可用send()和recv()传送数据。当会话结束后,调用close()。带外数据根据规定用send()和recv()来接收。 实现SOCK_STREAM类型套接口的通讯协议保证数据不会丢失也不会重复。如果终端协议有缓冲区空间,且数据不能在一定时间成功发送,则认为连接中断,其后续的调用也将以WSAETIMEOUT错误返回。 SOCK_DGRAM类型套接口允许使用sendto()和recvfrom()从任意端口发送或接收数据报。如果这样一个套接口用connect()与一个指定端口连接,则可用send()和recv()与该端口进行数据报的发送与接收。
(2)connect函数
函数原型:
int connect(intsockfd,const struct sockaddr *servaddr,socklen_t addrlen)
connect()用来将参数sockfd 的socket 连至参数serv_addr 指定的网络地址. 结构sockaddr请参考bind(). 参数addrlen 为sockaddr 的结构长度.
返回值:成功则返回0, 失败返回-1, 错误原因存于errno 中 。
(3)bind函数:
当一个套接字用socket()创建后,存在一个名字空间(地址族),但它没有被命名。bind()将套接字地址(包括本地主机地址和本地端口地址)与所创建的套接字号联系起来,即将名字赋予套接字,以指定本地半相关。其调用格式如下:
int PASCAL FAR bind(SOCKET s, const struct sockaddr FAR * name, int namelen);
参数s是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。参数name是赋给套接字s的本地地址(名字),其长度可变,结构随通信域的不同而不同。
namelen表明了name的长度。
如果没有错误发生,bind()返回0。否则返回值SOCKET_ERROR。
地址在建立套接字通信过程中起着重要作用,作为一个网络应用程序设计者对套接字地址结构必须有明确认识。例如,UNIX有一组描述套接字地址的数据结构,其中使用TCP/IP
协议的地址结构为:
struct sockaddr_in{
short sin_family; /*AF_INET*/
u_short sin_port; /*16位端口号,网络字节顺序*/
struct in_addr sin_addr; /*32位IP地址,网络字节顺序*/
char sin_zero[8]; /*保留*/
}
(4)accept函数:
accept()的调用格式如下:
SOCKET PASCAL FAR accept(SOCKET s, struct sockaddr FAR* addr, int FAR* addrlen);
参数s为本地套接字描述符,在用做accept()调用的参数前应该先调用过listen()。addr指向客户方套接字地址结构的指针,用来接收连接实体的地址。addr的确切格式由套接字创建时建立的地址族决定。addrlen为客户方套接字地址的长度(字节数)。
如果没有错误发生,accept()返回一个SOCKET类型的值,表示接收到的套接字的描述符。否则返回值INVALID_SOCKET。
accept()用于面向连接服务器。参数addr和addrlen存放客户方的地址信息。调用前,参数addr指向一个初始值为空的地址结构,而addrlen的初始值为0;调用accept()后,服务器等待从编号为s的套接字上接受客户连接请求,而连接请求是由客户方的connect()调用发出的。当有连接请求到达时,accept()调用将请求连接队列上的第一个客户方套接字地址及长度放入addr和addrlen,并创建一个与s有相同特性的新套接字号。新的套接字可用于处理服务器并发请求。
四个套接字系统调用,socket()、bind()、connect()、accept(),可以完成一个完全五元相关的建立。socket()指定五元组中的协议元,它的用法与是否为客户或服务器、是否面向连接无关。bind()指定五元组中的本地二元,即本地主机地址和端口号,其用法与是否面向连接有关:在服务器方,无论是否面向连接,均要调用bind();在客户方,若采用面向连接,则可以不调用bind(),而通过connect()自动完成。若采用无连接,客户方必须使用bind()以获得一个唯一的地址。
以上讨论仅对客户/服务器模式而言,实际上套接字的使用是非常灵活的,唯一需遵循的原则是进程通信之前,必须建立完整的相关。
(5)listen函数:
监听连接──listen()
此调用用于面向连接服务器,表明它愿意接收连接。listen()需在accept()之前调用,其调用格式如下:
int PASCAL FAR listen(SOCKET s, int backlog);
参数s标识一个本地已建立、尚未连接的套接字号,服务器愿意从它上面接收请求。
backlog表示请求连接队列的最大长度,用于限制排队请求的个数,目前允许的最大值为5。如果没有错误发生,listen()返回0。否则它返回SOCKET_ERROR。
listen()在执行调用过程中可为没有调用过bind()的套接字s完成所必须的连接,并建立长度为backlog的请求连接队列。
调用listen()是服务器接收一个连接请求的四个步骤中的第三步。它在调用socket()分配一个流套接字,且调用bind()给s赋于一个名字之后调用,而且一定要在accept()之前调用。
下面给出一个简单的TCP客户端服务器的例子:
服务器(server.cpp):
#include<iostream> #include<unistd.h> #include<stdio.h> #include<netinet/in.h> #include<arpa/inet.h> #include<sys/socket.h> #include<string.h> #define SERVER_PORT 5049 #define SERVER_IP "127.0.0.1" int main(int argc,char*argv[]) { int listenfd,sockconn; listenfd = socket(AF_INET,SOCK_STREAM,0); if(listenfd < 0) perror("socket"); struct sockaddr_in servaddr,cliaddr; servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = inet_addr(SERVER_IP); servaddr.sin_port = htons(SERVER_PORT); int res = bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)); if(res == -1) perror("bind"); if(listen(listenfd,3) < 0) perror("listen"); socklen_t clilen = sizeof(cliaddr); sockconn = accept(listenfd,(struct sockaddr *)&cliaddr,&clilen); if(sockconn == -1) perror("accept"); char sendbuf[256]; char recvbuf[256]; while(1) { printf("Ser:>"); scanf("%s",sendbuf); if(strncmp(sendbuf,"quit",4) == 0) break; send(sockconn,sendbuf,strlen(sendbuf) + 1,0); recv(sockconn,recvbuf,256,0); printf("Cli:>%s\n",recvbuf); } close(listenfd); return 0; }
#include<iostream> #include <netinet/in.h> #include<unistd.h> #include<sys/socket.h> #include<string.h> #include<stdio.h> #include<arpa/inet.h> #define SERV_PORT 5049 #define ADDR "127.0.0.1" int main(int argc,char*argv[]) { int sockfd; struct sockaddr_in servaddr; sockfd = socket(AF_INET,SOCK_STREAM,0); if(sockfd == -1) perror("socket"); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(SERV_PORT); servaddr.sin_addr.s_addr = inet_addr(ADDR); int sockconn = connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr)); if(sockconn == -1) perror("connect"); char sendbuf[256]; char recvbuf[256]; while(1) { recv(sockfd,recvbuf,256,0); printf("Ser:>%s\n",recvbuf); printf("Cli:>"); scanf("%s",sendbuf); if(strncmp(sendbuf,"quit",4) == 0) break; send(sockfd,sendbuf,strlen(sendbuf) + 1,0); } close(sockfd); return 0; }
从执行结果可以看出:该程序基本实现到了客户端与服务器的通信,但其中还存在了很多的问题比如:
(1)该程序只能实现一对一的通信,也就是说一个服务器只能对一个客户端服务。
(2)该程序只能是服务器发一句,客户端接收一句,不能实现服务器或客户端连着发的情况。
针对上述的问题我们给出一个升级的版本,可以实现上述的问题:
服务器:
#include"unp.h" pthread_t tid[2]; void *write_fun(void*arg) { int sockSer = *(int *)arg; char sendbuf[BUFF_SIZE]; memset(sendbuf,0,BUFF_SIZE); while(1) { printf(":>"); scanf("%s",sendbuf); if(strncmp(sendbuf,"quit",4) == 0) { break; } send(sockSer,sendbuf,strlen(sendbuf) + 1,0); } pthread_exit(NULL); } void * read_fun(void *arg) { int sockSer = *(int *)arg; char recvbuf[BUFF_SIZE]; memset(recvbuf,0,BUFF_SIZE); struct sockaddr_in addrCli; socklen_t addrlen = sizeof(struct sockaddr); while(1) { getsockname(sockSer,(struct sockaddr *)&addrCli,&addrlen); recv(sockSer,recvbuf,BUFF_SIZE,0); if(strncmp(recvbuf,"quit",4) == 0) { break; } printf("Client[%d]:>%s\n",addrCli.sin_port,recvbuf); } } int main(int argc,char*argv[]) { int sockSer; sockSer = socket(AF_INET,SOCK_STREAM,0); if(sockSer == -1) perror("socket"); struct sockaddr_in addrSer,addrCli; addrSer.sin_family = AF_INET; addrSer.sin_port = htons(SOCK_PORT); addrSer.sin_addr.s_addr = inet_addr(SERVER_IP); socklen_t addrlen = sizeof(struct sockaddr); int res = bind(sockSer,(struct sockaddr*)&addrSer,addrlen); if(res == -1) perror("bind"); listen(sockSer,QUEUE_SIZE); int sockConn; while(1) { sockConn = accept(sockSer,(struct sockaddr*)&addrCli,&addrlen); if(sockConn == -1) perror("accept"); else printf("client accept server ok.\n"); pthread_create(&tid[0],NULL,write_fun,&sockConn); pthread_create(&tid[1],NULL,read_fun,&sockConn); } close(sockSer); return 0; }
#include"unp.h" pthread_t tid[2]; void *write_fun(void*arg) { int sockCli = *(int *)arg; char sendbuf[BUFF_SIZE]; memset(sendbuf,0,BUFF_SIZE); while(1) { printf(":>"); scanf("%s",sendbuf); send(sockCli,sendbuf,strlen(sendbuf) + 1,0); if(strncmp(sendbuf,"quit",4) == 0) { pthread_cancel(tid[1]); break; } } pthread_exit(NULL); } void * read_fun(void *arg) { int sockCli = *(int *)arg; char recvbuf[BUFF_SIZE]; memset(recvbuf,0,BUFF_SIZE); struct sockaddr_in addrCli; socklen_t addrlen = sizeof(struct sockaddr); getsockname(sockCli,(struct sockaddr *)&addrCli,&addrlen); while(1) { recv(sockCli,recvbuf,BUFF_SIZE,0); printf("%s\n",recvbuf); } } int main(int argc,char*argv[]) { int sockCli; sockCli = socket(AF_INET,SOCK_STREAM,0); if(sockCli == -1) perror("socket"); struct sockaddr_in addrSer; addrSer.sin_family = AF_INET; addrSer.sin_port = htons(SOCK_PORT); addrSer.sin_addr.s_addr = inet_addr(SERVER_IP); int sockConn; socklen_t addrlen = sizeof(struct sockaddr); sockConn = connect(sockCli,(struct sockaddr*)&addrSer,addrlen); if(sockConn == -1) perror("accept"); else printf("client connect server ok.\n"); pthread_create(&tid[0],NULL,write_fun,&sockCli); pthread_create(&tid[1],NULL,read_fun,&sockCli); pthread_join(tid[0],NULL); pthread_join(tid[1],NULL); close(sockCli); return 0; }
从上述的执行结果可以看出,我们已经实现了服务器或客户端连着发的问题,但是在多个客户端通信时,遇到的问题就是服务器端无法判断是与哪一个客户端进行通信,对于这个问题我们在以后的博客中为大家解决。
标签:
原文地址:http://blog.csdn.net/ooooo12345re/article/details/51567962