CS模型:Client /Service模型
过程描述:
类比打电话。
对于服务器,首先创建套接字,socket;之后绑定一个端口,bind;进入监听状态,listen;等到对方打电话,accept;之后一直阻塞等到客户端连接过来。
对于客户端,首先要创建套接字,之后尝试打电话,即connect,一旦拨打通了,即连接上了,开始TCP的三次握手。(详细见下面分析)
建立连接后,客户端,和服务器听过write和read进行数据请求,和数据应答。都是write发,read收。
客户端想关闭,close,然后服务器,read后,也close。
TCP三次握手
回射客户/服务器
重要函数
socket:创建套接字,用于通信。
bind
注意这里的输入参数,通用地址结构。
listen:调用socket和bind函数之后,调用accept之前调用。
listen:是将套接字从主动套接字转化为被动套接字
主动套接字:发起连接 connect
被动套接字:接收连接 accept
对于给定的监听套接字,内核要维护两个队列:
1。 已有客户发出并到达服务器,服务器正在等待完成相应的TCP三路握手过程。
2。 已完成连接的队列。
accept
connect
服务器端:
//if ((conn=accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0)//服务端:等待客户端接入.conn对应的是客户端发出来的套接字
//{
// ERR_EXIT("accept");
//}
char recvbuf[1024];
while (1)
{
conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen); //如果没有错误产生,则accept()返回一个描述所接受包的SOCKET类型的值。否则的话,返回INVALID_SOCKET错误,应用程序可通过调用WSAGetLastError()来获得特定的错误代码。
cout << conn << endl;
memset(recvbuf, 0, sizeof(recvbuf));
char sendBuf[50];
sprintf(sendBuf, "Welcome %s to here!", inet_ntoa(peeraddr.sin_addr));
cout << sendBuf << endl;
send(conn, sendBuf, strlen(sendBuf) + 1, 0);
int ret=recv(conn, recvbuf, sizeof(recvbuf), 0);
cout << recvbuf << endl;
//send(conn, recvbuf, strlen(recvbuf), MSG_PEEK);
}
closesocket(conn);
客户端:
connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
send(sockClient, "hello", strlen("hello") + 1, 0);
char recvBuf[50];
recv(sockClient, recvBuf, 50, 0);
printf("%s\n", recvBuf);
此时服务器端的while(1)只执行一次,为啥?是因为accept()要收到数据包才能有返回值,而客户端只发送一次,所以while(1)里的accept在收到数据包一次后,就继续接收,进入等待。但是如果把accept放到外面,里面的while(1)就一直执行了,前提是客户端必须发送一次数据包,让accept函数执行完,才执行while(1)。这个很容易理解。
我觉得好的做法就是把accept放到while(1)里。这样客户端每次有新数据时,都会打印出来。但是经过测试发现我前面的理解有点不对。
while(1)里的accept是,只要我客户端关闭连接,里面就不执行了,因为accept一直在等待。但是如果我客户端修改成下面:
while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{
cout << send(client_sock, sendbuf, strlen(sendbuf), 0) << endl;//发送,并输出返回字符串长度
recv(client_sock, recvbuf, sizeof(recvbuf), 0);
cout << recvbuf << endl;
}
一直从stdin中输入,服务器只能接收第一次的数据并打印出来,后面的再次输入已经无法接收了。 原因我还没搞清楚。应该和accept的机制有关。
好的做法还是把accept放到外面。
if ((conn=accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0)//服务端:等待客户端接入.conn对应的是客户端发出来的套接字
{
ERR_EXIT("accept");
}
char recvbuf[1024];
while (1)
{
//conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen); //如果没有错误产生,则accept()返回一个描述所接受包的SOCKET类型的值。否则的话,返回INVALID_SOCKET错误,应用程序可通过调用WSAGetLastError()来获得特定的错误代码。
//三个参数:服务器的这边创建的套接字,接收到的数据包存储的套接字(类似于客户端的),长度peerlen。 conn:描述所接受包的SOCKET类型的值
cout << conn << endl;
memset(recvbuf, 0, sizeof(recvbuf));
char sendBuf[50];
sprintf(sendBuf, "Welcome %s to here!", inet_ntoa(peeraddr.sin_addr));//打印的是客户端的地址,并把给地址通过sendBuf给客户端发送过去
cout << sendBuf << endl;
send(conn, sendBuf, strlen(sendBuf) + 1, 0);//给客户端发
int ret=recv(conn, recvbuf, sizeof(recvbuf), 0);
cout << recvbuf << endl;
//send(conn, recvbuf, strlen(recvbuf), MSG_PEEK);
}
另外,客户端程序进行修改:
while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{
cout << send(client_sock, sendbuf, strlen(sendbuf), 0) << endl;//发送,并输出返回字符串长度
recv(client_sock, recvbuf, sizeof(recvbuf), 0);
cout << recvbuf << endl;
}
上述实现的是从客户端输入,服务器端显示出来。
服务器端编程的步骤
sockets(套接字)编程有三种,流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM),原始套接字(SOCK_RAW);基于TCP的socket编程是采用的流式套接字。
服务器端编程的步骤:
1:加载套接字库,创建套接字(WSAStartup()/socket());
2:绑定套接字到一个IP地址和一个端口上(bind());
3:将套接字设置为监听模式等待连接请求(listen());
4:请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept());
5:用返回的套接字和客户端进行通信(send()/recv());
6:返回,等待另一连接请求;
7:关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup())。
客户端编程的步骤:
1:加载套接字库,创建套接字(WSAStartup()/socket());
2:向服务器发出连接请求(connect());
3:和服务器端进行通信(send()/recv());
4:关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup())。
参考:http://www.cnblogs.com/Sniper-quay/archive/2011/06/22/2086636.html
(未完待续,有点乱)