TCP协议的服务器与客户端的程序设计(代码注释超详细)
在上篇博客中講到了三次握手和四次揮手:
Linux網絡編程--TCP中的三次握手和四次揮手_神廚小福貴!的博客-CSDN博客服務器編程和客戶端編程的大致流程如下:三次握手是在客戶端中的connect中完成的,具體如下:那么上述說到的SYN ACK這些是什么東西呢?上述的截圖取自《Linux高性能服務器編程》電子版的截圖!+根據書中所提到的在客戶端對服務器端connect的時候,由客戶端對服務器端發出一個SYN的請求連接的報文,值為i是32位序列號,然后服務器收到客戶端SYN之后,會反饋給客戶端一個自身服務器端的SYN報文和確認號報文ACK,其中ACK的值為客戶端和服務器端的SYN序...https://blog.csdn.net/qq_45829112/article/details/122278769?spm=1001.2014.3001.5501
?這篇我們來說一下TCP協議下服務器端和客戶端的程序設計!
先來看下客戶端和服務器端的設計原理:
?過程如上,具體的就是客戶端和服務器創建socket創建套接字,bind將本地地址和套接字綁在一起,listen創建監聽隊列,客戶端通過connect三次握手與服務器連接,然后服務器accept接受客戶端的連接請求,客戶端向服務器send數據,服務器再對客戶端send響應數據。大致流程就這樣,下面面來看代碼演示:
服務器端:
1.創建socket套接字
int sockfd = socket(AF_INET,SOCK_STREAM,0);
int?socket(int domain,int type, int protocol) 返回值為非負描述符的話-----成功,返回值為-1的話--失敗
?socket參數詳解:取自《Linux高性能服務器編程》
咱們測試用的是IPV4的網絡,所以用的是AF_INET(書上寫的是PF_INET,在Windows中AF_INET和PF_INET是一樣的)
2.bind將套接字綁定到一個地址
int res = bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen)
?bind函數的返回值:成功----返回0? ? ? ?失敗----返回-1
bind 參數詳解:sockfd為第一步socket創建套接字返回的描述符
????????const struct sockaddr *addr為指向一個struct sockaddr類型的結構體變量,此結構體成員用于設置要綁定的ip和端口
? ? ? ? addrlen為結構體大小
3.listen創建監聽隊列
int res = listen(sockfd,5);
listen參數詳解:
sockfd 為 創建套接字的返回描述符;
backlog為已完成連接隊列個數
4.accept在套接字已完成隊列中接收一個連接
int res = accept(sockfd,const struct sockaddr *addr,socklen_t addrlen);
accept參數的詳解:
?
參數:取自百度百科
5.recv和send
size_t recv(int sockfd,void* buf,size_t len,int flags);size_t send(int sockfd,const void* buf,size_t len,int flags);
??????參數詳解:
對于recv來說,sockfd為客戶端對應套接字,buf 為存放接收數據的地方,len為存放數據區的大小,flags一般置為0
對send來說:sockfd接收端的套接字描述符,要發送的消息,發送消息的大小,flags一般置為0
客戶端
客戶端只有一個connect在服務器端中沒有出現
connect參數詳解:
?sockfd是socket返回的描述符
那個saddr是服務器端的地址
len是服務器端的saddr大小
服務器端代碼演示
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>int main()
{int sockfd = socket(AF_INET,SOCK_STREAM,0);//創建套接字assert(sockfd != -1);struct sockaddr_in saddr,caddr; //sockaddr_in在頭文件#include<netinet/in.h>或#include <arpa/inet.h>中定義,saddr代表服務器端地址 caddr代表客戶端地址memset(&saddr,sizeof(saddr),0);//saddr其實有四項成員,最后一項用來占位的,必須搞為0,索性我們開始直接給全部置為0,后面再來綁定ip和端口saddr.sin_family = AF_INET;//地址族,TCP/ipv4協議族saddr.sin_port = htons(6000);//端口為小端序列,htons轉換為網絡字節序,也是大端字節序(一般使用都是5000以上,5000以內一般都是特定使用的,比如你辦了個手機卡,你能用110這個號碼嘛,博客園因為110有特殊意義,一個道理)saddr.sin_addr.s_addr = inet_addr("192.168.0.108");//自己本地的IP地址(ifconfig)//inet_addr將點分十進制轉換為午飯后整型int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));//將sockfd和本地IP綁定//為什么要這個呢(struct sockaddr*)強轉呢,bind這個參數類型為struct sockaddr與sockaddr_in類型不一致,所以強轉assert(res != -1);res = listen(sockfd,5);//監聽隊列assert(res != -1);while(1){int len = sizeof(caddr);int c = accept(sockfd,(struct sockaddr*)&caddr,&len); //accept參數中第三個參數為結構體大小的一個指針,所以前面求lenif( c < 0 ){continue;}printf("accept caddr = %d , caddr.ip = %s , caddr.port = %d \n",c,inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port));char buff[128] = {0};int n = recv(c,buff,127,0);printf("recv:(%d) = %s",n,buff);send(c,"ok",2,0);close(c);}
}
客戶端代碼演示
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>int main()
{int sockfd = socket(AF_INET,SOCK_STREAM,0);assert(sockfd != -1);struct sockaddr_in saddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(6000);saddr.sin_addr.s_addr = inet_addr("192.168.0.108");int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));//那個強轉的話,和服務器端呢個強轉是大同小異的assert (res != -1);char buff[128] = {0};printf("input: \n");fgets(buff,128,stdin);send(sockfd,buff,strlen(buff),0);memset(buff,0,128);recv(sockfd,buff,127,0);printf("buff = %s \n",buff);close(sockfd);exit(0);
}
這樣的話,簡單的服務器和客戶端的編程就完成了,下面來看運行結果:
?當然這次的編程代碼只能接受一個數據,想要接受多個數據,或者多線程共同訪問一個服務器的話,在這個基礎上改一下代碼即可,后面再出一篇文章來講這個東西!
這就是我對socket編程的理解,如有不到位的地方,歡迎各位指出,共同學習,共同進步!
“任何一個不曾起舞的日子,都是對未來的辜負!”
總結
以上是生活随笔為你收集整理的TCP协议的服务器与客户端的程序设计(代码注释超详细)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 手套多少钱啊?
- 下一篇: 内联函数inline