首先是要了解兩個概念
1 套接字的函數
socket函數
該函數的功能是創建一個套接字。
該函數的原型如下:
SOCKET socket ( int af,int type, int protocol );
af:表示一個地址家族,通常爲AF_INET(ipv4地址)。AF_INET6(ipv6地址) 跟PF_INET 一樣的 PF_INET6
tpe:表示套接字類型,如果爲SOCK_STREAM,表示創建面向連接的流式套接字(TCP);爲SOCK_DGRAM(UDP),表示創建面向無連接的數據報套接字;爲SOCK_RAW,表示創建原始套節字。對於這些值,用戶可以在Winsock2.h頭文件中找到。
potocol:表示套接口所用的協議,如果用戶不指定,可以設置爲0。
返回值:創建的套接字句柄。
失敗:返回-1
bind函數 :都是綁定自己的ip地址
將創建的套接字綁定(bind)到本地的地址和端口上
綁定一個套接字
一般來說,與客戶端的套接字關聯的地址沒有太大意義,可以讓系統默認的地址,
而對服務端需要給一個接收客戶端請求的套接字綁定一個衆所周知的地址.
int bind(int sockfd,const struct *addr,socklen_t len);
返回值:成功 0
失敗 -1
參數:sockfd:套接字的文件描述符
const struct *addr: 存放你要綁定的ip和端口號
struct sockaddr --》通用地址結構體,兼容ipv4 和ipv6
struct sockaddr_in --》ipv4地址結構體
struct sockaddr_in6 --》ipv6地址結構體
//綁定:使sockFd工作在固定的port 或 IP
struct sockaddr_in serAddr;//定義ipv4地址結構體
serAddr.sin_family=AF_INET;//地址協議類型 ipv4
serAddr.sin_port =htons(SER_PORT);//端口號:(注意網絡字節序) 主機h to(轉換) n(網絡) l(長整形) n(短整型)
//serAddr.sin_addr.s_addr=inet_addr(SER_IP);//字符串轉換爲網絡地址 將主機字節序ip-->>網絡字節序 也就是小端序變大端序
serAddr.sin_addr.s_addr=INADDR_ANY; //指本主機的所有IP 包括廣播組播的IP
//inet_ntoa(); 網絡地址轉換爲字符串
memset(serAddr.sin_zero,0,8);//bzero(serAddr.sin_zero,8);
.在進程運行的機器上,指定的地址必須有效,不能指定一個其他機器的地址
.地址必須和創建套接字時的地址族所支持的格式相匹配
.端口號不小於1024(1024以內的大多是系統的所以不小於1024),超級用戶除外
listen函數:
該函數的功能是將套接字設置爲監聽模式。對於流式套接字,必須處於監聽模式才能夠接收客戶端套接字的連接。
該函數的原型如下:
int listen ( SOCKET s, int backlog);
參數說明:
s:表示套接字標識。
backlog:表示等待連接的最大隊列長度。例如,如果backlog被設置爲2,此時有3個客戶端同時發出連接請求,那麼前2個客戶端連接會放置在等待隊列中,第3個客戶端會得到錯誤信息。
accpet函數:
該函數的功能是接受客戶端的連接。在流式套接字中,只有在套接字處於監聽狀態,才能接受客戶端的連接。
該函數的原型如下:
SOCKET accept ( SOCKET s, struct sockaddr FAR* addr, int FAR* addrlen );
參數說明:
s:是一個套接字,它應處於監聽狀態。
addr:是一個sockaddr_in結構指針,包含一組客戶端的端口號、IP地址等信息。
addrlen:用於接收參數addr的長度。
返回值:一個新的套接字,它對應於已經接受的客戶端連接,對於該客戶端的所有後續操作,都應使用這個新的套接字。
closesocket函數:
該函數的功能是關閉套接字。
該函數的原型如下:
int closesocket (SOCKET s);
參數說明:
s:標識一個套接字。如果參數s設置了SO_DONTLINGER選項,則調用該函數後會立即返回,但此時如果有數據尚未傳送完畢,則會繼續傳遞數據,然後才關閉套接字。
connect函數:
該函數的功能是發送一個連接請求。
該函數的原型如下:
int connect (SOCKET s,const struct sockaddr FAR* name,int namelen );
參數說明:
s:表示一個套接字。
name:表示套接字s要連接的主機地址和端口號。
namelen:是name緩衝區的長度。
返回值:如果函數執行成功,返回值爲0,否則爲SOCKET_ERROR。用戶可以通過WSAGETLASTERROR得到其錯誤描述。
htons函數:
該函數的功能是將一個16位的無符號短整型數據由主機排列方式轉換爲網絡排列方式。
該函數的原型如下:
uint32_t htonl(uint32_t hostlong);
參數說明:
hostshort:是一個主機排列方式的無符號短整型數據。
返回值:函數返回值是16位的網絡排列方式數據。
htonl函數:
該函數的功能是將一個無符號長整型數據由主機排列方式轉換爲網絡排列方式。
該函數的原型如下:
uint32_t htonl(uint32_t hostlong);
參數說明:
hostlong:表示一個主機排列方式的無符號長整型數據。
返回值:32位的網絡排列方式數據。
inet_addr函數:
該函數的功能是將一個由字符串表示的地址轉換爲32位的無符號長整型數據。
該函數的原型如下:
unsigned long inet_addr (const char FAR * cp);
in_addr_t inet_addr(const char *cp);
參數說明:
cp:表示一個IP地址的字符串。
返回值:32位無符號長整數。
大端ip—>小端ip src 英文:原 dst 英文:目標
const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);
char *inet_ntoa(struct in_addr in);
端口:大端—>小端
uint32_t ntohl(uint32_t netlong);
32位 4字節
uint16_t ntohs(uint16_t netshort);
16位 2字節
recv函數:
該函數的功能是從面向連接的套接字中接收數據。
該函數的原型如下:
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
參數說明:
s:表示一個套接字。
buf:表示接收數據的緩衝區。
len:表示buf的長度。
flags:表示函數的調用方式。如果爲MSG_PEEK,則表示查看傳來的數據,在序列前端的數據會被複制一份到返回緩衝區中,但是這個數據不會從序列中移走;如果爲MSG_OOB,則表示用來處理Out-Of-Band數據,也就是外帶數據。
send函數:
該函數的功能是在面向連接方式的套接字間發送數據。
該函數的原型如下:
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
參數說明:
s:表示一個套接字。
buf:表示存放要發送數據的緩衝區。
len:表示緩衝區長度。
flags:表示函數的調用方式。
recvfrom函數:
該函數用於接收一個數據報信息並保存源地址。
該函數的原型如下:
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
參數說明:
sockfd:表示準備接收數據的套接字。
buf:指向緩衝區的指針,用來接收數據。
len:表示緩衝區的長度。
flags:通過設置這個值可以影響函數調用的行爲。
src_addr:是一個指向地址結構的指針,用來接收發送數據方的地址信息。
addrlen:表示緩衝區的長度。
sendto函數:
該函數的功能是向一個特定的目的方發送數據。
該函數的原型如下:
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
sockfd:表示一個(可能已經建立連接的)套接字的標識符。
buf:指向緩衝區的指針,該緩衝區包含將要發送的數據。
len:表示緩衝區的長度。
flags:通過設置這個值可以影響函數調用的行爲。
dest_addr:指定目標套接字的地址。
addrlen:表示緩衝區的長度。
返回值:
WSACleanup函數:
該函數的功能是釋放爲Ws2_32.dll動態鏈接庫初始化時分配的資源。
該函數的原型如下:
int WSACleanup (void);
使用該函數關閉動態鏈接庫:
WSACleanup(); /關閉動態鏈接庫/
不再多說舉個例子吧
基於TCP的網絡聊天程序
服務端
按照以下順序編寫代碼:
(1)創建套接字。
(2)綁定套接字到本地的地址和端口上。
(3)設置套接字爲監聽狀態。
(4)接受請求連接的請求。
(5)進行通信。
(6)通信完畢,釋放套接字資源。
客戶端
按照以下順序編寫代碼:
(1)創建套接字。
(2)發出連接請求。
(3)請求連接後進行通信操作。
(4)釋放套接字資源。
通信之間都會有兩個服務進行通信,一個客戶端一個服務接收端
服務端接收
建立套接字
//1 建立套接
sockfd=socket(AF_INET,SOCK_STREAM,0);
//2 綁定一個套接字 就是使一個sockfd工作在一個固定的port或IP地址上
struct sockaddr_in serAddr;
serAddr.sin_family=AF_INET;//地址協議類型
serAddr.sin_port =htons(SER_PORT);//端口號:(注意網絡字節序)
serAddr.sin_addr.s_addr=INADDR_ANY; //指本主機的所有IP 包括廣播組播的IP
//inet_ntoa(); 網絡地址轉換爲字符串
memset(serAddr.sin_zero,0,8);//bzero(serAddr.sin_zero,8);
//將創建的套接字綁定(bind)到本地的地址和端口上
/*
綁定一個套接字
一般來說,與客戶端的套接字關聯的地址沒有太大意義,可以讓系統默認的地址,
而對服務端需要給一個接收客戶端請求的套接字綁定一個衆所周知的地址.
int bind(int sockfd,const struct *addr,socklen_t len);
.在進程運行的機器上,指定的地址必須有效,不能指定一個其他機器的地址
.地址必須和創建套接字時的地址族所支持的格式相匹配
.端口號不小於1024,超級用戶除外**/
ret=bind(sockfd,(struct sockaddr *)&serAddr,sizeof(serAddr));
if(ret==-1)
{
perror("bind");
shutdown(sockfd,SHUT_RDWR);
return 2;
}
//監聽:MAX_WAIT最大的半鏈接等待隊列大小 設置套接字的狀態爲監聽狀態(listen),準備接受客戶端的連接請求。
//服務器調listen宣告可接受連接請求
//int listen(int sockfd,int backlog); backlog :支持連接數
if(listen(sockfd,MAX_WAIT)==-1)
{
perror("listen");
shutdown(sockfd,SHUT_RDWR);
return 3;
}
printf("服務器啓動完成.\n");
//處理鏈接
int newFd;
char *erMsg="Sorry,Server Busy!";
pid_t pid;
struct sockaddr_in peer;//記錄對方的信息
socklen_t len=sizeof(peer);
while(1)
{
/*
* 沒有連接到來阻塞 如果連接到來則處理 成功返回建立連接的新套接字
*/
/*服務器調listen之後則可能調用accept建立連接
int accept(int sockfd,struct addr *peer_addr,socklen_t *len);
成功返回用以交換數據的套接字,該套接字連接到調用connect的客戶端
peer_addr 獲取連接方的標識信息,如果不關心可填NULL
如果沒有連接請求時,accept阻塞到一個請求的到來*/
//newFd=accept(sockfd,NULL,NULL);
newFd=accept(sockfd,(struct sockaddr *)&peer,&len);
if(newFd<0) continue;//error
//inet_addr()字符串轉換爲網絡地址 inet_ntoa()網絡地址轉換爲字符串; 網絡地址轉換爲字符串
//htons();端口號:(注意網絡字節序)由主機排列方式轉換爲網絡排列方式 ntohs()由網絡排列方式轉換爲主機排列方式
printf("from:%s<%d>\n",inet_ntoa(peer.sin_addr),ntohs(peer.sin_port));
//建立進程或線程對newFd服務 sockfd繼續監聽
pid=fork();//產生子進程
if(pid==-1)//錯誤
{
write(newFd,erMsg,strlen(erMsg));
shutdown(newFd,SHUT_RDWR);//關閉讀寫端
continue;
}
else if(pid==0)//child
{
doWork(newFd);//連接一個客戶端 就創建就創建子進程 對其進行操作
shutdown(newFd,SHUT_RDWR);//關閉讀寫端
exit(0);
}
}
以上是TCP的一個完整的聊天程序
接下來就是一個客戶端的
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>//for htons
#include <netinet/in.h>//inet_addr inet_ntoa
#include <signal.h>
#define SER_IP “192.168.2.112”
#define SER_PORT 10086
#define SIZE 1024
int main(void)
{
int sockfd,ret;
//建立套接
sockfd=socket(AF_INET,SOCK_STREAM,0);//PF_INET/AF_INET IPV4因特網域 SOCK_STREAM 有序 可靠 雙向的面向連接的字節流 (默認協議TCP/IP) protocol:可以通過該參數指定選擇一個特定的協議 ,一般填爲0 使用默認的協議
if(sockfd==-1)
{
perror("create socket");
return 1;
}
printf("sockfd:%d\n",sockfd);
//連接:初始要連接的對象信息
struct sockaddr_in serAddr;
serAddr.sin_family=AF_INET;//地址協議類型
serAddr.sin_port =htons(SER_PORT);//端口號:(注意網絡字節序)
serAddr.sin_addr.s_addr=inet_addr(SER_IP);//字符串轉換爲網絡地址
//inet_ntoa(); 網絡地址轉換爲字符串
memset(serAddr.sin_zero,0,8);//bzero(serAddr.sin_zero,8);
//如果處理的是面向連接的網絡服務(SOCK_STREAM 或 SOCK_SEQAPCKET)在交換數據以前,建立一個連接 向服務器發出連接請求(connect)。
// int connect(int sockfd,const struct sockaddr *perr_addr,socklen_t len);
ret=connect(sockfd,(struct sockaddr *)&serAddr,sizeof(serAddr));
if(ret==-1)
{
perror("connect");
shutdown(sockfd,SHUT_RDWR);//how:SHUT_RD 關閉讀寫端
return 2;
}
//
char buf[SIZE+1];
char str[]="hello";
while(1)
{
ret=read(sockfd,buf,SIZE);//阻塞 讀取數據
if(ret==0) break;//distconnect
else if(ret==-1)
{
perror("read error");
break;
}
buf[ret]='\0';
printf("%s\n",buf);
}
shutdown(sockfd,SHUT_RDWR);//close(sockfd);關閉讀寫端
return 0;
}
大致就是這麼操作的有疑問可以提出來哈哈