9月4日,腾讯微博团队发布消息宣布,从9月28日23点59分起,腾讯微博将中止运营,届时用户将没法登陆。此消息一出,不只当即在微信朋友圈刷屏,还成功地在其曾经的对手——新浪微博上刷了一波热搜。编程
我看到这个消息时,不由感到惊讶:什么?腾讯微博还在运营?我分明记得在2014年时,腾讯微博团队都已经解散了,真没想到在这以后它居然还支撑了这么久!虽然今日破败至此,但遥想当年,腾讯微博也是阔过的。腾讯为了对其注入流量,用QQ对其进行导流。在你注册QQ帐号时,也就自动开通了微博帐号,所以腾讯所坐拥巨大流量。这种在线的我的日记让每个人都有机会在网上自由地表达本身的观点,十分受人欢迎。服务器
在不少的评论中,人们都习惯于把QQ、微信和微博都笼统地称为社交软件。然而,我认为它们也算是一种聊天交流的工具,不过,咱们怎么用C语言制做一个简单的聊天程序呢?微信
首先咱们从TCP/IP协议开始介绍。网络
TCP/IP协议介绍socket
TCP/IP协议包含的范围很是的广,它是一种四层协议,包含了各类硬件、软件需求的定义。TCP/IP协议确切的说法应该是TCP/UDP/IP协议。UDP协议(User Datagram Protocol 用户数据报协议),是一种保护消息边界的,不保障可靠数据的传输。TCP协议(Transmission Control Protocol 传输控制协议),是一种流传输的协议。他提供可靠的、有序的、双向的、面向链接的传输。函数
保护消息边界,就是指传输协议把数据看成一条独立的消息在网上传输,接收端只能接收独立的消息。也就是说存在保护消息边界,接收端一次只能接收发送端发出的一个数据包。工具
而面向流则是指无保护消息边界的,若是发送端连续发送数据,接收端有可能在一次接收动做中,会接收两个或者更多的数据包。学习
举例来讲,假如,咱们连续发送三个数据包,大小分别是2k、4k、8k,这三个数据包都已经到达了接收端的网络堆栈中,若是使用UDP协议,无论咱们使用多大的接收缓冲区去接收数据,咱们必须有三次接收动做,才可以把全部的数据包接收完。而使用TCP协议,咱们只要把接收的缓冲区大小设置在14k以上,咱们就可以一次把全部的数据包接收下来,只须要有一次接收动做。测试
这就是由于UDP协议的保护消息边界使得每个消息都是独立的。而流传输,却把数据看成一串数据流,它不认为数据是一个一个的消息。因此有不少人在使用TCP协议通信的时候,并不清楚TCP是基于流的传输,当连续发送数据的时候,他们时常会认为TCP会丢包。其实否则,由于当它们使用的缓冲区足够大时,它们有可能会一次接收到两个甚至更多的数据包,而不少人每每会忽视这一点,只解析检查了第一个数据包,而已经接收的其它据包却被忽略了。计算机网络
TCP/IP协议与WinSock网络编程接口的关系
WinSock 并非一种网络协议,它只是一个网络编程接口,也就是说,它不是协议,可是它能够访问不少种网络协议,你能够把它看成一些协议的封装。如今的 WinSock已经基本上实现了与协议无关。你可使用WinSock来调用多种协议的功能。那么,WinSock和TCP/IP协议究竟是什么关系呢?实际上,WinSock就是TCP/IP协议的一种封装,你能够经过调用WinSock的接口函数来调用TCP/IP的各类功能.例如我想用TCP/IP 协议发送数据,你就可使用WinSock的接口函数Send()来调用TCP/IP的发送数据功能,至于具体怎么发送数据,WinSock已经帮你封装好了这种功能。
WinSock编程简单流程
WinSock编程分为服务器端和客户端两部分,TCP服务器端的大致流程以下:
对于任何基于WinSock的编程首先必需要初始化WinSock DLL库。
int WSAStarup( WORD wVersionRequested,LPWSADATA lpWsAData )。
wVersionRequested是咱们要求使用的WinSock的版本。
调用这个接口函数能够初始化WinSock 。
而后必须建立一个套接字(Socket)。
SOCKET Socket(int af,int type,int protocol);
套接字能够说是WinSock通信的核心。WinSock通信的全部数据传输,都是经过套接字来完成的,套接字包含了两个信息,一个是IP地址,一个是Port端口号,使用这两个信息,就能够肯定网络中的任何一个通信节点。
当调用了Socket()接口函数建立了一个套接字后,必须把套接字与你须要进行通信的地址创建联系,能够经过绑定函数bind来实现这种联系。
int bind(SOCKET s,const struct sockaddr FAR* name,int namelen) ;
struct sockaddr_in{
short sin_family ;
u_short sin_port;
struct in_addr sin_addr ;
char sin_sero[8] ;
}
就包含了须要创建链接的本地的地址,包括地址族、IP和端口信息。sin_family字段必须把它设为AF_INET,这是告诉WinSock使用的是IP地址族。sin_port就是要用来通信的端口号。sin_addr就是要用来通信的IP地址信息。
在这里,必须还得提一下有关'大头(big-endian)'小头(little-endian)'。由于各类不一样的计算机处理数据时的方法是不同的,Intel X86处理器上是用'小头'形式来表示多字节的编号,就是把低字节放在前面,把高字节放在后面,而互联网标准却正好相反,因此,必须把主机字节转换成网络字节的顺序。WinSock API提供了几个函数。
把主机字节转化成网络字节的函数;
u_long htonl(u_long hostlong);
u_short htons(u_short hostshort);
把网络字节转化成主机字节的函数;
u_long ntohl(u_long netlong);
u_short ntohs(u_short netshort) ;
这样,设置IP地址和port端口时,就必须把主机字节转化成网络字节后,才能用Bind()函数来绑定套接字和地址。
当绑定完成以后,服务器端必须创建一个监听的队列来接收客户端的链接请求。
int listen(SOCKET s,int backlog);
这个函数能够把套接字转成监听模式。
若是客户端有了链接请求,咱们还必须使用
int accept(SOCKET s,struct sockaddr FAR* addr,int FAR* addrlen);
来接受客户端的请求。
如今基本上已经完成了一个服务器的创建,而客户端的创建的流程则是初始化WinSock,而后建立Socket套接字,再使用
int connect(SOCKET s,const struct sockaddr FAR* name,int namelen) ;
来链接服务端。
下面是一个最简单的建立服务器端和客户端的例子:
服务器端的建立:
WSADATA wsd;
SOCKET sListen;
SOCKET sclient;
UINT port = 800;
int iAddrSize;
struct sockaddr_in local , client;
WSAStartup( 0x11 , &wsd );
sListen = Socket ( AF_INET , SOCK_STREAM , IPPOTO_IP );
local.sin_family = AF_INET;
local.sin_addr = htonl( INADDR_ANY );
local.sin_port = htons( port );
bind( sListen , (struct sockaddr*)&local , sizeof( local ) );
listen( sListen , 5 );
sClient = accept( sListen , (struct sockaddr*)&client , &iAddrSize );
客户端的建立:
WSADATA wsd;
SOCKET sClient;
UINT port = 800;
char szIp[] = "127.0.0.1";
int iAddrSize;
struct sockaddr_in server;
WSAStartup( 0x11 , &wsd );
sClient = Socket ( AF_INET , SOCK_STREAM , IPPOTO_IP );
server.sin_family = AF_INET;
server.sin_addr = inet_addr( szIp );
server.sin_port = htons( port );
connect( sClient , (struct sockaddr*)&server , sizeof( server ) );
当服务器端和客户端创建链接之后,不管是客户端,仍是服务器端均可以使用
int send( SOCKET s,const char FAR* buf,int len,int flags);
int recv( SOCKET s,char FAR* buf,int len,int flags);
函数来接收和发送数据,由于,TCP链接是双向的。
当要关闭通信链接的时候,任何一方均可以调用
int shutdown(SOCKET s,int how);
来关闭套接字的指定功能,再调用
int closeSocket(SOCKET s) ;
来关闭套接字句柄,这样一个通信过程就算完成了。
能够参考教材计算机网络(第6版)295页图6-32所示的系统调用使用顺序:
注意:上面的代码没有任何检查函数返回值,若是你作网络编程就必定要检查任何一个WinSock API函数的调用结果,由于不少时候函数调用并不必定成功。上面介绍的函数,返回值类型是int的话,若是函数调用失败的话,返回的都是SOCKET_ERROR。
VC中socket编程
·服务器实现
服务器端编程的步骤:
1:加载套接字库,建立套接字(WSAStartup()/socket());
2:绑定套接字到一个IP地址和一个端口上(bind());
3:将套接字设置为监听模式等待链接请求(listen());
4:请求到来后,接受链接请求,返回一个新的对应于这次链接的套接字(accept());
5:用返回的套接字和客户端进行通讯(send()/recv());
6:返回,等待另外一链接请求;
7:关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup())。
服务器端代码以下:
#include <stdio.h>
#include <Winsock2.h>
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return;
}
if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) {
WSACleanup( );
return;
}
SOCKET sockSrv=socket(AF_INET,SOCK_STREAM,0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);
bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
listen(sockSrv,5);
SOCKADDR_IN addrClient;
int len=sizeof(SOCKADDR);
while(1){
SOCKET sockConn=accept(sockSrv,(SOCKADDR*)&addrClient,&len);
char sendBuf[50];
sprintf(sendBuf,"Welcome %s to here!",inet_ntoa(addrClient.sin_addr));
send(sockConn,sendBuf,strlen(sendBuf)+1,0);
char recvBuf[50];
recv(sockConn,recvBuf,50,0);
printf("%s\n",recvBuf);
closesocket(sockConn);
}
}
·客户端实现
客户端编程的步骤:
1:加载套接字库,建立套接字(WSAStartup()/socket());
2:向服务器发出链接请求(connect());
3:和服务器端进行通讯(send()/recv());
4:关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup())。
客户端的代码以下:
#include <stdio.h>
#include <Winsock2.h>
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return;
}
if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) {
WSACleanup( );
return;
}
SOCKET sockClient=socket(AF_INET,SOCK_STREAM,0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);
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);
closesocket(sockClient);
WSACleanup();
}
测试结果