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();
}
測試結果