C語言之網路程式設計(伺服器和客戶端)
1、 套接字:源IP地址和目的IP地址以及源埠號和目的埠號的組合稱為套接字。其用於標識客戶端請求的伺服器和服務。
常用的TCP/IP協議的3種套接字型別如下所示。
(1)流套接字(SOCK_STREAM):
流套接字用於提供面向連線、可靠的資料傳輸服務。該服務將保證資料能夠實現無差錯、無重複傳送,並按順序接收。流套接字之所以能夠實現可靠的資料服務,原因在於其使用了傳輸控制協議,即TCP(The Transmission ControlProtocol)協議。
(2) 資料報套接字(SOCK_DGRAM):
資料報套接字提供了一種無連線的服務。該服務並不能保證資料傳輸的可靠性,資料有可能在傳輸過程中丟失或出現數據重複,且無法保證順序地接收到資料。資料報套接字使用UDP(User
Datagram Protocol)協議
(3) 原始套接字(SOCK_RAW):(一般不用這個套接字)
原始套接字(SOCKET_RAW)允許對較低層次的協議直接訪問,比如IP、 ICMP協議,它常用於檢驗新的協議實現,或者訪問現有服務中配置的新裝置,因為RAW SOCKET可以自如地控制Windows下的多種協議,能夠對網路底層的傳輸機制進行控制,所以可以應用原始套接字來操縱網路層和傳輸層應用。比如,我們可以通過RAW SOCKET來接收發向本機的ICMP、IGMP協議包,或者接收TCP/IP棧不能夠處理的IP包,也可以用來發送一些自定包頭或自定協議的IP包。網路監聽技術很大程度上依賴於SOCKET_RAW
2、 套接字基本函式:
(1) 建立套接字:int socket(int family, int type, intprotocol);
功能介紹:
在Linux作業系統中,一切皆檔案,這個大家都知道,個人理解建立socket的過程其實就是一個獲得檔案描述符的過程,當然這個過程會是比較複雜的。可以從核心中找到建立socket的程式碼,並且socket的建立和其他的listen,bind等操作分離開來。socket函式完成正確的操作是返回值大於0的檔案描述符,當返回小於0的值時,操作錯誤。同樣是返回一個檔案描述符,但是會因為三個引數組合不同,對於資料具體的工作流程不同,對於應用層程式設計來說,這些也是不可見的。
引數說明:
從socket建立的函式可以看出,socket有三個引數,family代表一個協議族,比較熟知的就是AF_INET,PF_PACKET等;第二個引數是協議型別,常見型別是SOCK_STREAM,SOCK_DGRAM, SOCK_RAW, SOCK_PACKET等;第三個引數是具體的協議,對於標準套接字來說,其值是0,對於原始套接字來說就是具體的協議值。
(2) 套接字繫結函式: intbind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
功能介紹:
bind函式主要應用於伺服器模式一端,其主要的功能是將addrlen長度 structsockaddr型別的myaddr地址與sockfd檔案描述符繫結到一起,在sockaddr中主要包含伺服器端的協議族型別,網路地址和埠號等。在客戶端模式中不需要使用bind函式。當bind函式返回0時,為正確繫結,返回-1,則為繫結失敗。
引數說明:
bind函式的第一個引數sockfd是在建立socket套接字時返回的檔案描述符。
bind函式的第二個引數是structsockaddr型別的資料結構,由於structsockaddr資料結構型別不方便設定,所以通常會通過對tructsockaddr_in進行地質結構設定,然後進行強制型別轉換成structsockaddr型別的資料,
(3) 監聽函式:int listen(int sockfd, int backlog);
功能介紹:
剛開始理解listen函式會有一個誤區,就是認為其操作是在等在一個新的connect的到來,其實不是這樣的,真正等待connect的是accept操作,listen的操作就是當有較多的client發起connect時,server端不能及時的處理已經建立的連線,這時就會將connect連線放在等待佇列中快取起來。這個等待佇列的長度有listen中的backlog引數來設定。listen和accept函式是伺服器模式特有的函式,客戶端不需要這個函式。當listen執行成功時,返回0;執行失敗時,返回值位-1.
引數說明:
sockfd是前面socket建立的檔案描述符;backlog是指server端可以快取連線的最大個數,也就是等待佇列的長度。
(4) 請求接收函式: int accept(int sockfd, structsockaddr *client_addr, socklen_t *len);
功能介紹:
接受函式accept其實並不是真正的接受,而是客戶端向伺服器端監聽埠發起的連線。對於TCP來說,accept從阻塞狀態返回的時候,已經完成了三次握手的操作。Accept其實是取了一個已經處於connected狀態的連線,然後把對方的協議族,網路地址以及埠都存在了client_addr中,返回一個用於操作的新的檔案描述符,該檔案描述符表示客戶端與伺服器端的連線,通過對該檔案描述符操作,可以向client端傳送和接收資料。同時之前socket建立的sockfd,則繼續監聽有沒有新的連線到達本地埠。返回大於0的檔案描述符則表示accept成功,否則失敗。
引數說明:
sockfd是socket建立的檔案描述符;client_addr是本地伺服器端的一個structsockaddr型別的變數,用於存放新連線的協議族,網路地址以及埠號等;第三個引數len是第二個引數所指內容的長度,對於TCP來說其值可以用sizeof(structsockaddr_in)來計算大小,說要說明的是accept的第三個引數要是指標的形式,因為這個值是要傳給協議棧使用的。
(5)客戶端請求連線函式: intconnect(int sock_fd, struct sockaddr *serv_addr,int addrlen);
功能介紹:
連線函式connect是屬於client端的操作函式,其目的是向伺服器端傳送連線請求,這也是從客戶端發起TCP三次握手請求的開始,伺服器端的協議族,網路地址以及埠都會填充到connect函式的serv_addr地址當中。當connect返回0時說明已經connect成功,返回值是-1時,表示connect失敗。
引數說明:
connect的第一個引數是socket建立的檔案描述符;第二個引數是一個structsockaddr型別的指標,這個引數中設定的是要連線的目標伺服器的協議族,網路地址以及埠號;第三個引數表示第二個引數內容的大小,與accept不同,這個值不是一個指標。
在伺服器端和客戶端建立連線之後是進行資料間的傳送和接收,主要使用的接收函式是recv和read,傳送函式是send和write。因為對於socket套接字來說,最終實際操作的是檔案描述符,所以可以使用對檔案進行操作的接收和傳送函式對socket套接字進行操作。read和write函式是檔案程式設計裡的知識,所以這裡不再做多與的贅述。
3、 有了以上的知識,那麼我們就可以編寫一個簡單的伺服器和客戶端了
(1) 簡易伺服器:這個伺服器只能與一個客戶端相連線,如果有多個客戶端就不能用這個伺服器進行連線。
程式碼:
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#define PORT 9990 //埠號
#define SIZE 1024 //定義的陣列大小
int Creat_socket() //建立套接字和初始化以及監聽函式
{
int listen_socket = socket(AF_INET, SOCK_STREAM, 0); //建立一個負責監聽的套接字
if(listen_socket == -1)
{
perror("socket");
return -1;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET; /* Internet地址族 */
addr.sin_port = htons(PORT); /* 埠號 */
addr.sin_addr.s_addr = htonl(INADDR_ANY); /* IP地址 */
int ret = bind(listen_socket, (struct sockaddr *)&addr, sizeof(addr)); //連線
if(ret == -1)
{
perror("bind");
return -1;
}
ret = listen(listen_socket, 5); //監聽
if(ret == -1)
{
perror("listen");
return -1;
}
return listen_socket;
}
int wait_client(int listen_socket)
{
struct sockaddr_in cliaddr;
int addrlen = sizeof(cliaddr);
printf("等待客戶端連線。。。。\n");
int client_socket = accept(listen_socket, (struct sockaddr *)&cliaddr, &addrlen); //建立一個和客戶端交流的套接字
if(client_socket == -1)
{
perror("accept");
return -1;
}
printf("成功接收到一個客戶端:%s\n", inet_ntoa(cliaddr.sin_addr));
return client_socket;
}
void hanld_client(int listen_socket, int client_socket) //資訊處理函式,功能是將客戶端傳過來的小寫字母轉化為大寫字母
{
char buf[SIZE];
while(1)
{
int ret = read(client_socket, buf, SIZE-1);
if(ret == -1)
{
perror("read");
break;
}
if(ret == 0)
{
break;
}
buf[ret] = '\0';
int i;
for(i = 0; i < ret; i++)
{
buf[i] = buf[i] + 'A' - 'a';
}
printf("%s\n", buf);
write(client_socket, buf, ret);
if(strncmp(buf, "end", 3) == 0)
{
break;
}
}
close(client_socket);
}
int main()
{
int listen_socket = Creat_socket();
int client_socket = wait_client(listen_socket);
hanld_client(listen_socket, client_socket);
close(listen_socket);
return 0;
}
(2) 多程序併發伺服器:該伺服器就完全彌補了上一個伺服器的不足,可以同時處理多個客戶端,只要有客戶端來連線它,他就能響應。在我們這個伺服器中,父程序主要負責監聽,所以在父程序一開始就要把父程序的接收函式關閉掉,防止父程序在接收函式處阻塞,導致子程序不能建立成功。同理,子程序主要負責接收客戶端,並做相關處理,所以子程序在一建立就要把監聽函式關閉,不然會導致伺服器功能的紊亂。這個伺服器有一個特別要注意的是,子程序在退出時會產生殭屍程序,所以我們一定要對子程序退出後進行處理。
程式碼:
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#define PORT 9990
#define SIZE 1024
int Creat_socket() //建立套接字和初始化以及監聽函式
{
int listen_socket = socket(AF_INET, SOCK_STREAM, 0); //建立一個負責監聽的套接字
if(listen_socket == -1)
{
perror("socket");
return -1;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET; /* Internet地址族 */
addr.sin_port = htons(PORT); /* 埠號 */
addr.sin_addr.s_addr = htonl(INADDR_ANY); /* IP地址 */
int ret = bind(listen_socket, (struct sockaddr *)&addr, sizeof(addr)); //連線
if(ret == -1)
{
perror("bind");
return -1;
}
ret = listen(listen_socket, 5); //監聽
if(ret == -1)
{
perror("listen");
return -1;
}
return listen_socket;
}
int wait_client(int listen_socket)
{
struct sockaddr_in cliaddr;
int addrlen = sizeof(cliaddr);
printf("等待客戶端連線。。。。\n");
int client_socket = accept(listen_socket, (struct sockaddr *)&cliaddr, &addrlen); //建立一個和客戶端交流的套接字
if(client_socket == -1)
{
perror("accept");
return -1;
}
printf("成功接收到一個客戶端:%s\n", inet_ntoa(cliaddr.sin_addr));
return client_socket;
}
void hanld_client(int listen_socket, int client_socket) //資訊處理函式,功能是將客戶端傳過來的小寫字母轉化為大寫字母
{
char buf[SIZE];
while(1)
{
int ret = read(client_socket, buf, SIZE-1);
if(ret == -1)
{
perror("read");
break;
}
if(ret == 0)
{
break;
}
buf[ret] = '\0';
int i;
for(i = 0; i < ret; i++)
{
buf[i] = buf[i] + 'A' - 'a';
}
printf("%s\n", buf);
write(client_socket, buf, ret);
if(strncmp(buf, "end", 3) == 0)
{
break;
}
}
close(client_socket);
}
void handler(int sig)
{
while (waitpid(-1, NULL, WNOHANG) > 0)
{
printf ("成功處理一個子程序的退出\n");
}
}
int main()
{
int listen_socket = Creat_socket();
signal(SIGCHLD, handler); //處理子程序,防止殭屍程序的產生
while(1)
{
int client_socket = wait_client(listen_socket); //多程序伺服器,可以建立子程序來處理,父程序負責監聽。
int pid = fork();
if(pid == -1)
{
perror("fork");
break;
}
if(pid > 0)
{
close(client_socket);
continue;
}
if(pid == 0)
{
close(listen_socket);
hanld_client(listen_socket, client_socket);
break;
}
}
close(listen_socket);
return 0;
}
(3) 多執行緒併發伺服器:上一個多程序伺服器有一個缺點,就是每當一個子程序得到響應的時候,都要複製父程序的一切資訊,這樣就導致了CPU資源的浪費,當客戶端有很多來連線這個伺服器的時候,就會產生很多的子程序,會導致伺服器的響應變得很慢。所以我們就想到了多執行緒併發伺服器,我們知道執行緒的速度是程序的30倍左右,所以我們就用執行緒來做伺服器。
程式碼: #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> #define PORT 9990 #define SIZE 1024 int Creat_socket() //建立套接字和初始化以及監聽函式 { int listen_socket = socket(AF_INET, SOCK_STREAM, 0); //建立一個負責監聽的套接字 if(listen_socket == -1) { perror("socket"); return -1; } struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; /* Internet地址族 */ addr.sin_port = htons(PORT); /* 埠號 */ addr.sin_addr.s_addr = htonl(INADDR_ANY); /* IP地址 */ int ret = bind(listen_socket, (struct sockaddr *)&addr, sizeof(addr)); //連線 if(ret == -1) { perror("bind"); return -1; } ret = listen(listen_socket, 5); //監聽 if(ret == -1) { perror("listen"); return -1; } return listen_socket; } int wait_client(int listen_socket) { struct sockaddr_in cliaddr; int addrlen = sizeof(cliaddr); printf("等待客戶端連線。。。。\n"); int client_socket = accept(listen_socket, (struct sockaddr *)&cliaddr, &addrlen); //建立一個和客戶端交流的套接字 if(client_socket == -1) { perror("accept"); return -1; } printf("成功接收到一個客戶端:%s\n", inet_ntoa(cliaddr.sin_addr)); return client_socket; } void hanld_client(int listen_socket, int client_socket) //資訊處理函式,功能是將客戶端傳過來的小寫字母轉化為大寫字母 { char buf[SIZE]; while(1) { int ret = read(client_socket, buf, SIZE-1); if(ret == -1) { perror("read"); break; } if(ret == 0) { break; } buf[ret] = '\0'; int i; for(i = 0; i < ret; i++) { buf[i] = buf[i] + 'A' - 'a'; } printf("%s\n", buf); write(client_socket, buf, ret); if(strncmp(buf, "end", 3) == 0) { break; } } close(client_socket); } int main() { int listen_socket = Creat_socket(); while(1) { int client_socket = wait_client(listen_socket); pthread_t id; pthread_create(&id, NULL, hanld_client, (void *)client_socket); //建立一個執行緒,來處理客戶端。 pthread_detach(id); //把執行緒分離出去。 } close(listen_socket); return 0; }
(4)客戶端:客戶端相對於伺服器來說就簡單多了,客戶端只需要建立和伺服器相連線的套接字,然後對其初始化,然後再進行連線就可以了,連線上伺服器就可以傳送你想傳送的資料了。
程式碼:
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#define PORT 9990
#define SIZE 1024
int main()
{
int client_socket = socket(AF_INET, SOCK_STREAM, 0); //建立和伺服器連線套接字
if(client_socket == -1)
{
perror("socket");
return -1;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET; /* Internet地址族 */
addr.sin_port = htons(PORT); /* 埠號 */
addr.sin_addr.s_addr = htonl(INADDR_ANY); /* IP地址 */
inet_aton("127.0.0.1", &(addr.sin_addr));
int addrlen = sizeof(addr);
int listen_socket = connect(client_socket, (struct sockaddr *)&addr, addrlen); //連線伺服器
if(listen_socket == -1)
{
perror("connect");
return -1;
}
printf("成功連線到一個伺服器\n");
char buf[SIZE] = {0};
while(1) //向伺服器傳送資料,並接收伺服器轉換後的大寫字母
{
printf("請輸入你相輸入的:");
scanf("%s", buf);
write(client_socket, buf, strlen(buf));
int ret = read(client_socket, buf, strlen(buf));
printf("buf = %s", buf);
printf("\n");
if(strncmp(buf, "END", 3) == 0) //當輸入END時客戶端退出
{
break;
}
}
close(listen_socket);
return 0;
}
相關推薦
C語言之網路程式設計(伺服器和客戶端)
1、 套接字:源IP地址和目的IP地址以及源埠號和目的埠號的組合稱為套接字。其用於標識客戶端請求的伺服器和服務。 常用的TCP/IP協議的3種套接字型別如下所示。 (1)流套接字(SOCK_STREAM): 流套接字用於提供面向連線、可靠的資料傳輸服務。該服務將保證資料能夠實現無差錯、無重複傳送,並按順序接
C語言之網路程式設計(一)域名解析
在網路程式設計時,知道域名是不能直接訪問一個主機的,需要轉換成相應的IP地址。有時在程式中需要將一個IP地址轉換成一個域名。本節將講解C程式中的IP地址與域名的轉換問題。 提示:在TCP/IP網路中,通訊雙方的主機必須知道彼此的IP地址方可進行正常的通訊,如果給出的主機的域
簡單的TCP協議 socket程式設計(C語言版伺服器和客戶端)
最近由於本人對網路程式設計的喜愛,所以對一點關於TCP協議socket程式設計的總結。由於本人的能力有限,寫的可能過於簡單,只適合初學者,歡迎大牛提出寶貴的意見,本人會感激不盡的。廢話少說了,進入正題。(下面程式碼是基於VC6.0) 下圖
C#.網路程式設計 Socket基礎(一)Socket TCP協議 實現端到端(伺服器與客戶端)簡單字串通訊
簡介: 本章節主要討論了Socket的入門知識,還未針對Socket的難點問題(比如TCP的無訊息邊界問題)展開討論,往後在其他章節中進行研究。 注意點: 伺服器(比如臺式電腦)的IP為1.1.1.2,那麼客戶端(其他裝置,比如手機,Ipad)連線的一定是
C#.網路程式設計 Socket基礎(三) 基於WinForm系統Socket TCP協議 實現端到端(伺服器與客戶端).txt.word.png等不同型別檔案傳輸
一、簡介: 前面的兩篇介紹了字串傳輸、圖片傳輸: 其實,本文針對Socket基礎(二)進一步完成,以便可以進行多種檔案傳輸。 二、基於不同的流(檔案流、記憶體流、網路等)讀寫。 1、圖片傳輸 方法一:(在客戶端用檔案流傳送(即將圖片寫到檔案流去,以便傳送),
Windows C語言 UDP程式設計 server端(伺服器、客戶端)--初級(簡單版)
UDP協議全稱是使用者資料報協議[1] ,在網路中它與TCP協議一樣用於處理資料包,是一種無連線的協議。在OSI模型中,在第四層——傳輸層,處於IP協議的上一層。UDP有不提供資料包分組、組裝和不能對資料包進行排序的缺點,也就是說,當報文傳送之後,是無法得知其
Linux學習之網路程式設計(TCP相關基礎知識)
言之者無罪,聞之者足以戒。 - “詩序”、 1、網路位元組序: 在TCP的編寫過程中需要從網路位元組序轉換到主機位元組序,當然也需要從主機位元組序轉換到網路位元組序 htons 把 unsigned short型別從主機序轉換到網路序htonl把unsigned long型別從
libevent簡單介紹以及使用(帶有伺服器和客戶端)
這兩天使用了下libevent,只使用了網路IO事件(EV_READ和 EV_WRITE),查閱了下libevent的介面文件,這裡做點筆記,並附上程式碼,開發環境是win7+vs2010 這裡只介紹需要用到的libevent的介面函式,更多介面函式請檢視libevent官方文件 如果想了解
socket網路程式設計中伺服器與客戶端通訊失敗的問題
伺服器程式碼如下 public class server { public static void main(String[] args) throws IOException { ServerSocket server=new S
網路程式設計(二)——伺服器和客戶端資訊的獲取
目錄 1、字串IP地址和二進位制IP地址結構的轉換 2.套接字檔案描述符的判定 3、IP地址與域名之間的相互轉換 4、協議名稱處理函式 1、字串IP地址和二進位制IP地址結構的轉換 #include <sys/socket.h> #inclu
Linux學習之網路程式設計(多程序併發伺服器)
言之者無罪,聞之者足以戒。 - “詩序” 上面我們所說過的通訊都是一個伺服器一個客戶端之間的通訊,下面我們來交流一下多程序併發伺服器的相關知識 邏輯上就是這個樣子的,就是一個伺服器多個客戶端進行資料的傳輸。 1、傳送資料的函式: ssize_t send(int sockfd,
Linux學習之網路程式設計(epoll的用法)
言之者無罪,聞之者足以戒。 - “詩序” epoll相關的函式包含在標頭檔案<sys/epoll.h> epoll是Linux核心為處理大批量控制代碼而作了改進的poll,是Linux下多路複用IO介面select/poll的增強版本,它能顯著減少程式在大量併發連線中只有少量活躍
Linux學習之網路程式設計(select)
言之者無罪,聞之者足以戒。 - “詩序” 1、阻塞式I/O 下面看一下實現的邏輯: 2、非阻塞式I/O 下面看一下實現的邏輯: 3、I/O複用(select/epoll) (1) int select (int maxfdp, fd_set
Linux學習之網路程式設計(UDP程式設計)
言之者無罪,聞之者足以戒。 - “詩序” 前幾篇文章說的都是TCP通訊的問題,這篇文章說一下UDP通訊的相關內容: 下面來看一下基於UDP的獲取時間的客戶伺服器的程式框圖: (1)、建立一個基於IPv4(AF_INET)的資料報套接字(SOCK_DGRAM) (2)、ssize
Linux學習之網路程式設計(TCP程式設計 模型總結)
言之者無罪,聞之者足以戒。 - “詩序” TCP通訊也就是伺服器和客戶端的一種通訊方式,它的整體框架為: 針對TCP通訊所用到的函式,我來做一下說明: (1)插座創造一個套接字 int socket(int domain,int type,int protocol) 標頭檔
Linux學習之網路程式設計(TCP三次握手四次揮手)
言之者無罪,聞之者足以戒。 - “詩序” 1、三次握手: 看一下三次握手的框圖: (1)、伺服器必須準備好接受外來連線 (2)、客戶端呼叫connect來主動開啟一個連線,此時客戶端TCP將會發送一個SYN分節 (3)、伺服器必須確認客戶的SYN (4)、客戶必須確認伺
Linux學習之網路程式設計(TCP程式設計)
言之者無罪,聞之者足以戒。 - “詩序” 1,TCP是什麼? TCP傳輸控制協議 向用戶程序提供可靠的全雙工位元組流(位元組流:給每一個位元組編序) 2,UDP是什麼? UDP使用者資料報協議 是一種無連線的協議 3,獲取時間服務的客戶端 (1),建立一個的的I
C語言之指標筆記(1)
指標(pointer)是C語言中最重要的概念之一,用於儲存變數的地址。 1.&運算子 (1)一元&運算子可給出變數的儲存地址。如過pooh是變數名,那麼&pooh是變數地址。 (2)如下例所示,使用運算子檢視不同函式中的同名變數儲存在什麼位置。 原始碼: //檢視
在Scala中使用函數語言程式設計(函式和高階函式)
圖示,這是一個普通
C語言面向物件程式設計(一):封裝與繼承
最近在用 C 做專案,之前用慣了 C++ ,轉回頭來用C 還真有點不適應。 C++ 語言中自帶面向物件支援,如封裝、繼承、多型等面向物件的基本特徵。 C 原本是面向過程的語言,自身沒有內建這些特性,但我們還是可以利用 C 語言本身已有的特性來實現面向物件的一些基本特徵。接下來