socket程式設計 send() recv() sendto() recvfrom()
原文:https://blog.csdn.net/keen_zuxwang/article/details/72872802
socket程式設計 send() recv() sendto() recvfrom()
int socket( int af, int type, int protocol);
af:
指定一個協議簇(協議域),常見有AF_INET──指定為IPv4協議,AF_INET6──指定為IPv6,AF_LOCAL──指定為UNIX 協議域等。
它值都是系統預先定義的巨集,系統支援哪些協議我們才可以使用,否則會呼叫失敗。協議簇是網路層的協議
type:
指定socket型別,常用的socket型別有:TCP(SOCK_STREAM)、UDP(SOCK_DGRAM)、SOCK_SEQPACKET、SOCK_RAW等,分別表明位元組流、資料報、有序分組、原始套介面。
這實際上是指定核心為我們提供的服務抽象(需要注意的,並不是每一種協議簇都支援這裡的所有的型別,所以型別與協議簇要匹配)。
protocol:
指定相應的傳輸協議,也就是諸如TCP或UDP協議等等,系統針對每一個協議簇與型別提供了一個預設的協議(protocol設定為0使用預設協議)。常用的協議有:
IPPROTO_TCP、IPPROTO_UDP、IPPROTO_STCP、IPPROTO_TIPC等,它們分別對應TCP傳輸協議、UDP傳輸協議、STCP傳輸協議、TIPC傳輸協議。
SOCK_STREAM 型別:
提供有序的、可靠的、雙向的和基於連線的位元組流,使用帶外資料傳送機制,為Internet地址族使用TCP。
SOCK_STREAM型別的套介面為全雙向的位元組流。對於流類套介面,在接收或傳送資料前必需處於已連線狀態。
用connect()呼叫建立與另一套介面的連線,連線成功後,即可用send()和recv()傳送資料。
當會話結束後,呼叫closesocket()。帶外資料根據規定用send()和recv()來接收。
實現SOCK_STREAM型別套介面的通訊協議保證資料不會丟失也不會重複。如果終端協議有緩衝區空間,且資料不能在一定時間成功傳送,則認為連線中斷,其後續的呼叫也將以WSAETIMEOUT錯誤返回。
SOCK_DGRAM 型別:
支援無連線的、不可靠的和使用固定大小(通常很小)緩衝區的資料報服務,為Internet地址族使用UDP。
SOCK_DGRAM型別套介面允許使用sendto()和recvfrom()從任意埠傳送或接收資料報。如果這樣一個套介面用connect()與一個指定埠連線,則可用send()和recv()與該埠進行資料報的傳送與接收
一般情況:
send(),recv()用於TCP,
sendto()及recvfrom()用於UDP
,但是send(),recv()也可以用於UDP,sendto()及recvfrom()也可以用於TCP
1、
int send(SOCKET s, const char FAR *buf, int len, int flags);
功能:向TCP連線的另一端傳送資料,客戶程式一般用send函式向伺服器傳送請求,而伺服器則通常用send函式來向客戶程式傳送應答。
s:指定傳送端套接字描述符;
buf: 指明一個存放應用程式要傳送資料的緩衝區;
len: 指明實際要傳送的資料的位元組數;
flags: 一般置0。
只描述同步Socket的send函式的執行流程:
1、當呼叫該函式時,send先比較待發送資料的長度len和套接字s的傳送緩衝的長度,
如果len大於s的傳送緩衝區的長度,該函式返回SOCKET_ERROR;
如果len小於或者等於s的傳送緩衝區的長度,那麼send先檢查協議是否正在傳送s的傳送緩衝中的資料,如果是就等待協議把資料傳送完,
如果協議還沒有開始傳送s的傳送緩衝中的資料或者s的傳送緩衝中沒有資料,那麼send就比較s的傳送緩衝區的剩餘空間和len,
如果len大於剩餘空間大小send就一直等待協議把s的傳送緩衝中的資料傳送完,如果len小於剩餘空間大小send就僅僅把buf中的資料copy到剩餘空間裡
(send僅僅是把buf中的資料copy到s的傳送緩衝區的剩餘空間裡,協議完成傳輸)。
2、
如果send函式copy資料成功,就返回實際copy的位元組數,
如果send在copy資料時出現錯誤,那麼send就返回SOCKET_ERROR;
如果send在等待協議傳送資料時網路斷開的話,那麼send函式也返回SOCKET_ERROR。
注意:
1、send函式把buf中的資料成功copy到s的傳送緩衝的剩餘空間裡後它就返回了,但是此時這些資料並不一定馬上被傳到連線的另一端。如果協議在後續的傳送過程中出現網路錯誤的話,
那麼下一個Socket函式就會返回SOCKET_ERROR。
2、每一個除send外的Socket函式在執行的最開始總要先等待套接字的傳送緩衝中的資料被協議傳送完畢才能繼續,如果在等待時出現網路錯誤,那麼該Socket函式就返回SOCKET_ERROR
在Unix系統下,如果send在等待協議傳送資料時網路斷開的話,呼叫send的程序會接收到一個SIGPIPE訊號,程序對該訊號的預設處理是程序終止。
(針對unix/linux核心的系統,程式設計者去處理好此種情形,不然程式會意外終止)。
2、
int recv(SOCKET s, char FAR *buf, int len, int flags);
功能:從TCP連線的另一端接收資料,客戶端、伺服器端應用程式都用recv函式接收資料
s:指定接收端套接字描述符;
buf:指明一個緩衝區,該緩衝區用來存放recv函式接收到的資料;
len:指明buf的長度;
flags:一般置0。
只描述同步Socket的recv函式的執行流程:
1、當呼叫recv函式時,recv先等待s的傳送緩衝中的資料被協議傳送完畢,
如果協議在傳送s的傳送緩衝中的資料時出現網路錯誤,那麼recv函式返回SOCKET_ERROR,
如果s的傳送緩衝中沒有資料或者資料被協議成功傳送完畢後,recv先檢查套接字s的接收緩衝區,
如果s接收緩衝區中沒有資料或者協議正在接收資料,那麼recv就一直等待,直到協議把資料接收完畢。
2、當協議把資料接收完畢,recv函式就把s的接收緩衝中的資料copy到buf中(協議接收到的資料可能大於buf的長度,
這時就要呼叫幾次recv函式才能把s的接收緩衝中的資料copy完。recv函式僅是copy出資料,真正的接收資料是協議完成),
3、recv函式返回其實際copy的位元組數。
如果recv在copy時出錯,那麼它返回SOCKET_ERROR;
如果recv函式在等待協議接收資料時網路中斷了,那麼它返回0。
注意:
在Unix系統下,如果recv函式在等待協議接收資料時網路斷開了,那麼呼叫recv的程序會接收到一個SIGPIPE訊號,程序對該訊號的預設處理是程序終止
(針對unix/linux核心的系統,程式設計者去處理好此種情形,不然程式會意外終止)。
3、
//無連線的資料報socket方式下,由於本地socket並沒有與遠端機器建立連線,所以在傳送資料時應指明目的地址
int sendto(int sockfd, const void *msg,int len, unsigned int flags, const struct sockaddr *to, int tolen);
to: 表示目地機的IP地址和埠號資訊,
tolen: 常常被賦值為sizeof(struct sockaddr)。
sendto 函式也返回實際傳送的資料位元組長度或在出現傳送錯誤時返回-1。
4、
int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen);
from:是一個struct sockaddr型別的變數,該變數儲存源機的IP地址及埠號。
fromlen: 常置為sizeof (struct sockaddr)。
當recvfrom()返回時,fromlen包含實際存入from中的資料位元組數。recvfrom()函式返回接收到的位元組數或當出現錯誤時返回-1,並置相應的errno。
說明:
1、sendto()和recvfrom()一般用於UDP協議中, 但是如果在TCP中connect函式呼叫後,它們也可以用於TCP傳輸:
2、對於資料報socket呼叫了connect()函式時,也可以利用send()和recv()進行資料傳輸,但該socket仍然是資料報socket,並且利用傳輸層的UDP服務。
但在傳送或接收資料報時,核心會自動為之加上目地和源地址資訊
使用示例:udp server 端程式:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define UDP_TEST_PORT 10000
int main(int argC, char* arg[])
{
struct sockaddr_in addr;
int sockfd, len = 0;
int addr_len = sizeof(struct sockaddr_in);
char buffer[256];
//建立socket,型別為SOCK_DGRAM
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror ("socket");
exit(1);
}
// 填寫sockaddr_in 結構
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(UDP_TEST_PORT); // 將16位主機字元順序轉換成網路字元順序
addr.sin_addr.s_addr = htonl(INADDR_ANY) ;// 接收任意IP發來的資料
// 繫結socket
if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr))<0) {
perror("connect");
exit(1);
}
while(1) {
bzero(buffer, sizeof(buffer));
len = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&addr ,&addr_len);
// 顯示client端的網路地址和收到的字串訊息
printf("Received a string from client %s, string is: %s\n", inet_ntoa(addr.sin_addr), buffer);
// 將收到的字串訊息返回給client端
sendto(sockfd,buffer, len, 0, (struct sockaddr *)&addr, addr_len);
}
return 0;
}
udp client 端程式:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define UDP_TEST_PORT 10000
//#define UDP_SERVER_IP "127.0.0.1"
int main(int argC, char* arg[])
{
struct sockaddr_in addr;
int sockfd, len = 0;
int addr_len = sizeof(struct sockaddr_in);
char buffer[256];
// 建立socket,注意必須是SOCK_DGRAM
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("socket");
exit(1);
}
// 填寫sockaddr_in
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(UDP_TEST_PORT); // 將16位主機字元順序轉換成網路字元順序
addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 網路地址字串轉換成網路所使用的二進位制數字(無符號長整型)
strcpy(buffer, "Hello World!");
len = sizeof(buffer);
while(1) {
//bzero(buffer, sizeof(buffer));
//printf("Please enter a string to send to server: \n");
// 從標準輸入裝置取得字串
//len = read(STDIN_FILENO, buffer, sizeof(buffer));
// 將字串傳送給server端
sendto(sockfd, buffer, len, 0, (struct sockaddr *)&addr, addr_len);
// 接收server端返回的字串
len = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&addr, &addr_len);
printf("Receive from server: %s\n", buffer);
msleep(1000);
}
return 0;
}