咱們經過TCP/IP來實現多人聊天室,若是租一個服務器咱們就能夠實現全網的多人聊天室(不懂tcp/ip的點進來http://www.javashuo.com/article/p-kirsmylw-de.html)!首先咱們要了解一下一些知識:html
1.socket的IO操做:http://www.javashuo.com/article/p-rxuukycn-do.htmlios
socket在鏈接後會有不少的發送和接收的消息若是咱們採用阻塞的方式是很不方便的,咱們肯本不知道用戶會在何時進行這些操做,而socket的IO操做就至關於快遞站同樣加入有這樣的消息,就會通知咱們來取,在這裏咱們採用的select方式這種IO方式是經過不斷的查詢,下面是對select函數的參數解釋,咱們們能夠吧咱們關注的對象加入一個數組select會將有消息的自動篩選出來。數組
咱們最主要採用的IO方式如下是對select函數的解釋:
int select (
int nfds,
fd_set FAR * readfds,
fd_set FAR * writefds,
fd_set FAR * exceptfds,
const struct timeval FAR * timeout
);
第一個參數nfds沒有用,僅僅爲與伯克利Socket兼容而提供。
readfds指定一個Socket數組(應該是一個,但這裏主要是表現爲一個Socket數組),select檢查該數組中的全部Socket。若是成功返回,則readfds中存放的是符合‘可讀性’條件的數組成員(如緩衝區中有可讀的數據)。
writefds指定一個Socket數組,select檢查該數組中的全部Socket。若是成功返回,則writefds中存放的是符合‘可寫性’條件的數組成員(如鏈接成功)。
exceptfds指定一個Socket數組,select檢查該數組中的全部Socket。若是成功返回,則cxceptfds中存放的是符合‘有異常’條件的數組成員(如鏈接接失敗)。
timeout指定select執行的最長時間,若是在timeout限定的時間內,readfds、writefds、exceptfds中指定的Socket沒有一個符合要求,就返回0。服務器
2.多線程操做:http://www.javashuo.com/article/p-nnmgamai-dt.html網絡
多線程的方式在網絡通訊中也是十分重要的這樣咱們才能夠同時實現收發,或者是其餘的一些功能。多線程
下面就是代碼部分絕對好用!socket
這裏咱們要注意的是若是咱們採用的是阻塞的方式recv,那麼必定同步的,假如咱們再recv以前就已經send了,那麼咱們將永遠不會受到以前的信息,將會永遠的阻塞在這裏,因此我建議當咱們recv以前發送一個同步信號(本身設置)給服務器,服務器在發送就能夠啦tcp
.cpp文件函數
// win_clint.cpp: 定義控制檯應用程序的入口點。 // #include "public.h" int main() { client user; user.process(); return 0; } client::client() { user = 0; writing = 0; serverAddr.sin_family = PF_INET; serverAddr.sin_port = SERVER_PORT; serverAddr.sin_addr.s_addr = inet_addr(SERVER_IP);//將字符串類型轉換uint32_t } void client::init() { int Ret; WSADATA wsaData; // 用於初始化套接字環境 // 初始化WinSock環境 if ((Ret = WSAStartup(MAKEWORD(2, 2), &wsaData)) != 0) { printf("WSAStartup() failed with error %d\n", Ret); WSACleanup(); } user= socket(AF_INET, SOCK_STREAM, 0);//採用ipv4,TCP傳輸 if (user <= 0) { perror("establish client faild"); printf("Error at socket(): %ld\n", WSAGetLastError()); exit(1); }; printf("establish succesfully\n");//建立成功 //阻塞式的等待服務器鏈接 if (connect(user, (const sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) { perror("connect to server faild"); printf("Error at socket(): %ld\n", WSAGetLastError()); exit(1); } printf("connect IP:%s Port:%d succesfully\n", SERVER_IP, SERVER_PORT);//建立成功 } void client::process() { char recvbuf[1024]; fd_set fdread,fedwrite; FD_ZERO(&fdread);//將fds清零 FD_ZERO(&fedwrite);//將fds清零 init(); while (1) { FD_SET(user, &fdread); if(writing==0) FD_SET(user, &fedwrite); struct timeval timeout = { 1,0 };//每一個Select等待三秒 switch (select(0, &fdread, &fedwrite, NULL, &timeout)) { case -1: { //perror("select"); printf("Error at socket(): %ld\n", WSAGetLastError()); /*exit(1);*/ break; } case 0: { //printf("select timeout......"); break; } default: { if (FD_ISSET(user, &fdread))//則有讀事件 { int size = recv(user, recvbuf, sizeof(recvbuf) - 1, 0); if (size > 0) { printf("server:%s\n", recvbuf); memset(recvbuf, '\0', sizeof(recvbuf)); } else if (size == 0) { printf("server is closed\n"); exit(1); } } if (FD_ISSET(user, &fedwrite)) { FD_ZERO(&fedwrite);//將fedwrite清零 writing = 1;//表示正在寫做 thread sendtask(bind(&client::sendata, this)); sendtask.detach();//將子線程和主進程分離且互相不影響 } break; } } } } void client::sendata() { char sendbuf[1024]; char middle[1024]; cin.getline(sendbuf, 1024);//讀取一行 send(user, sendbuf, sizeof(sendbuf) - 1, 0); writing = 0; }
.h文件ui
#ifndef PUBLIC_H #define PUBLIC_H //頭文件引用 #include<conio.h> #include <iostream> #include <thread> #include <winsock2.h> #include <stdio.h> #include<ws2tcpip.h>//定義socklen_t #pragma comment(lib, "WS2_32") // 連接到WS2_32.lib using namespace std; #define SERVER_IP "127.0.0.1"// 默認服務器端IP地址 #define SERVER_PORT 8888// 服務器端口號 class client { public: client(); void init(); void process(); private: int user; int writing; sockaddr_in serverAddr;//IPV4的地址方式包括服務端地址族、服務端IP地址、服務端端口號 void sendata(); }; #endif // !PUBLIC_H
.cpp
#include "public.h" int main() { server ser; ser.process(); return 0; } server::server() { listener = 0; serverAddr.sin_family = PF_INET; serverAddr.sin_port = SERVER_PORT; serverAddr.sin_addr.s_addr = inet_addr(SERVER_IP);//將字符串類型轉換uint32_t } //初始化函數,功能建立監聽套接字,綁定端口,並進行監聽 void server::init() { int Ret; WSADATA wsaData; // 用於初始化套接字環境 // 初始化WinSock環境 if ((Ret = WSAStartup(MAKEWORD(2, 2), &wsaData)) != 0) { printf("WSAStartup() failed with error %d\n", Ret); WSACleanup(); } listener = socket(AF_INET, SOCK_STREAM, 0);//採用ipv4,TCP傳輸 if (listener == -1) { printf("Error at socket(): %ld\n", WSAGetLastError()); perror("listener failed"); exit(1); } printf("建立成功\n"); unsigned long ul = 1; if (ioctlsocket(listener, FIONBIO, (unsigned long*)&ul) == -1) { perror("ioctl failed"); exit(1); }; /////////////////////////////////////////////////////////////////// //中間的參數綁定的地址若是是IPV4則是/////////////////// //struct sockaddr_in { // sa_family_t sin_family; /* address family: AF_INET */ // in_port_t sin_port; /* port in network byte order */ // struct in_addr sin_addr; /* internet address */ //}; //Internet address. //struct in_addr { // uint32_t s_addr; /* address in network byte order */ //} ///////////////////////////////////////////////////////////////// if (bind(listener, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) { perror("bind error"); exit(1); } if (listen(listener, 6) < 0) { perror("listen failed"); exit(1); }; socnum.push_back(listener);//將監聽套接字加入 } void server::process() { int mount = 0; fd_set fds; FD_ZERO(&fds);//將fds清零 init(); //下面就是不斷的檢查 printf("正在等待中\n"); while (1) { mount= socnum.size(); //將fds每次都從新賦值 for (int i = 0; i<mount; ++i) { FD_SET(socnum[i], &fds); } struct timeval timeout = { 1,0 };//每一個Select等待三秒 switch (select(0, &fds, NULL, NULL, &timeout)) { case -1: { perror("select\n"); printf("Error at socket(): %ld\n", WSAGetLastError()); printf("%d\n",mount); /* for (int i = 0; i < mount; ++i) { printf("%d\n", socnum[i]); }*/ Sleep(1000); break; } case 0: { //printf("select timeout......"); break; } default: { //將數組中的每個套接字都和剩餘的額套接字進行比較獲得當前的任務 for (int i = 0; i < mount; ++i) { //若是第一個套接字可讀的消息。就要創建鏈接 if (i == 0 && FD_ISSET(socnum[i], &fds)) { struct sockaddr_in client_address; socklen_t client_addrLength = sizeof(struct sockaddr_in); //返回一個用戶的套接字 int clientfd = accept(listener, (struct sockaddr*)&client_address, &client_addrLength); //添加用戶,服務器上顯示消息,並通知用戶鏈接成功 socnum.push_back(clientfd); printf("connect sucessfully\n"); char ID[1024]; sprintf(ID,"You ID is:%d", clientfd); char buf[30]="welcome to yskn's chatroom\n"; strcat(ID,buf); send(clientfd, ID, sizeof(ID) - 1, 0);//減去最後一個'/0' } if (i != 0 && FD_ISSET(socnum[i], &fds)) { char buf[1024]; memset(buf, '\0', sizeof(buf)); int size = recv(socnum[i], buf, sizeof(buf) - 1, 0); //檢測是否斷線 if (size == 0 || size == -1) { printf("remote client close,size is%d\n", size); //closesocket(socnum[i]);//先關閉這個套接字 FD_CLR(socnum[i], &fds);//在列表列表中刪除 socnum.erase(socnum.begin()+i);//在vector數組中刪除 } //如果沒有掉線 else { printf("clint %d says: %s \n", socnum[i], buf); //發送給每一個用戶 for (int j = 1; j < mount; j++) { char client[1024]; sprintf(client,"client %d:", socnum[i]); strcat(client, buf); send(socnum[j], client, sizeof(client) - 1, 0);//若是 } } } } break; } } } }
.h文件
#ifndef PUBLIC_H #define PUBLIC_H //頭文件引用 #include <winsock2.h> #include <stdio.h> #include <vector> #include<ws2tcpip.h>//定義socklen_t using namespace std; #pragma comment(lib, "WS2_32") // 連接到WS2_32.lib #define SERVER_IP "127.0.0.1"// 默認服務器端IP地址 #define SERVER_PORT 8888// 服務器端口號 class server { public: server(); void init(); void process(); private: int listener;//監聽套接字 sockaddr_in serverAddr;//IPV4的地址方式 vector <int> socnum;//存放建立的套接字 }; #endif // !PUBLIC_H