以前學C語言時學過,現在學C++時可以複習,重新鞏固下基礎知識。
什麼是套接字:
TCP用主機的IP地址加上主機上的端口號作爲TCP連接的端點,這種端點就叫做套接字(socket)或插口。套接字用(IP地址:端口號)表示。
套接字分類:
- 流式套接字(SOCK_STREAM):流式套接字用於提供面向連接、可靠的數據傳輸服務。該服務將保證數據能夠實現無差錯、無重複發送,並按順序接收。流式套接字之所以能夠實現可靠的數據服務,原因在於其使用了傳輸控制協議,即TCP(The Transmission Control Protocol)協議。
- 數據報套接字(SOCK_DGRAM):數據報套接字提供了一種無連接的服務。該服務並不能保證數據傳輸的可靠性,數據有可能在傳輸過程中丟失或出現數據重複,且無法保證順序地接收到數據。數據報套接字使用UDP(User Datagram Protocol)協議進行數據的傳輸。由於數據報套接字不能保證數據傳輸的可靠性,對於有可能出現的數據丟失情況,需要在程序中做相應的處理。
- 原始套接字(SOCK_RAW):原始套接字(SOCKET_RAW)允許對較低層次的協議直接訪問,比如IP、 ICMP協議,它常用於檢驗新的協議實現,或者訪問現有服務中配置的新設備,因爲RAW SOCKET可以自如地控制Windows下的多種協議,能夠對網絡底層的傳輸機制進行控制,所以可以應用原始套接字來操縱網絡層和傳輸層應用。比如,我們可以通過RAW SOCKET來接收發向本機的ICMP、IGMP協議包,或者接收TCP/IP棧不能夠處理的IP包,也可以用來發送一些自定包頭或自定協議的IP包。網絡監聽技術很大程度上依賴於SOCKET_RAW。
網絡通信一般使用CS模式連接。
網絡模型架構:
TCP/UDP屬於傳輸層。
TCP網絡通信:
TCP(Transmission Control Protocol 傳輸控制協議)是一種面向連接的、可靠的、基於字節流的傳輸層通信協議,由IETF的RFC 793定義。在簡化的計算機網絡OSI模型中,它完成第四層傳輸層所指定的功能,用戶數據報協議(UDP)是同一層內 [1] 另一個重要的傳輸協議。在因特網協議族(Internet protocol suite)中,TCP層是位於IP層之上,應用層之下的中間層。不同主機的應用層之間經常需要可靠的、像管道一樣的連接,但是IP層不提供這樣的流機制,而是提供不可靠的包交換。
通信流程圖如下
TCP三次握手建立連接:
- 第一次握手:客戶端發送syn包(seq=x)到服務器,並進入SYN_SEND狀態,等待服務器確認;
- 第二次握手:服務器收到syn包,必須確認客戶的SYN(ack=x+1),同時自己也發送一個SYN包(seq=y),即SYN+ACK包,此時服務器進入SYN_RECV狀態;
- 第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=y+1),此包發送完畢,客戶端和服務器進入ESTABLISHED狀態,完成三次握手。
TCP四次握手斷開連接:
- 第一次握手:主動關閉方發送一個FIN,用來關閉主動方到被動關閉方的數據傳送,也就是主動關閉方告訴被動關閉方:我已經不會再給你發數據了(當 然,在fin包之前發送出去的數據,如果沒有收到對應的ack確認報文,主動關閉方依然會重發這些數據),但此時主動關閉方還可以接受數據。
- 第二次握手:被動關閉方收到FIN包後,發送一個ACK給對方,確認序號爲收到序號+1(與SYN相同,一個FIN佔用一個序號)。
- 第三次握手:被動關閉方發送一個FIN,用來關閉被動關閉方到主動關閉方的數據傳送,也就是告訴主動關閉方,我的數據也發送完了,不會再給你發數據了。
- 第四次握手:主動關閉方收到FIN後,發送一個ACK給被動關閉方,確認序號爲收到序號+1,至此,完成四次揮手。
代碼示例服務端(使用了select 模式,目前只可以進行一對一連接,如果想一對多的話,添加多線程即可)
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#include <iostream>
#define SERVER_PORT 6555
int main() {
fd_set rfds;
struct timeval tv;
int retval, maxfd; //選擇器
/*創建socket*/
int ss = socket(AF_INET, SOCK_STREAM, 0); //AF_INET IPV4 ;SOCK_STREAM TCP
struct sockaddr_in server_sockaddr;
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(SERVER_PORT);
server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
/*bind*/
if(bind(ss, (struct sockaddr* ) &server_sockaddr, sizeof(server_sockaddr))==-1) {
perror("bind");
exit(1);
}
/*listen*/
if(listen(ss, 5) == -1) {
perror("listen");
exit(1);
}
/*connect*/
struct sockaddr_in client_addr;
socklen_t length = sizeof(client_addr);
///成功返回非負描述字,出錯返回-1
int conn = accept(ss, (struct sockaddr*)&client_addr, &length); //目測需要客戶端部分的addr
if( conn < 0 ) {
perror("connect");
exit(1);
}
while(1) {
/*把可讀文件描述符的集合清空*/
FD_ZERO(&rfds);
/*把標準輸入的文件描述符加入到集合中*/
FD_SET(0, &rfds);
maxfd = 0;
/*把當前連接的文件描述符加入到集合中*/
FD_SET(conn, &rfds);
/*找出文件描述符集合中最大的文件描述符*/
if(maxfd < conn)
maxfd = conn;
/*設置超時時間*/
tv.tv_sec = 5;
tv.tv_usec = 0;
/*等待聊天*/
retval = select(maxfd+1, &rfds, NULL, NULL, &tv);
if(retval == -1){
printf("select出錯,客戶端程序退出\n");
break;
}else if(retval == 0){
printf("服務端沒有任何輸入信息,並且客戶端也沒有信息到來,waiting...\n");
continue;
}else{
/*客戶端發來了消息*/
if(FD_ISSET(conn,&rfds)){
char buffer[1024];
memset(buffer, 0 ,sizeof(buffer));
int len = recv(conn, buffer, sizeof(buffer), 0);
if(strcmp(buffer, "exit\n") == 0) break;
printf("%s", buffer);
//send(conn, buffer, len , 0);把數據回發給客戶端
}
/*用戶輸入信息了,開始處理信息併發送*/
if(FD_ISSET(0, &rfds)){
char buf[1024];
fgets(buf, sizeof(buf), stdin);
//printf("you are send %s", buf);
send(conn, buf, sizeof(buf), 0);
}
}
}
close(conn);
close(ss);
return 0;
}
客戶端:
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>
#define MYPORT 6555
#define BUFFER_SIZE 1024
int main()
{
int sock_cli;
fd_set rfds;
struct timeval tv;
int retval, maxfd;
///定義sockfd
sock_cli = socket(AF_INET,SOCK_STREAM, 0);
///定義sockaddr_in
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(MYPORT); ///服務器端口
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); ///服務器ip
//連接服務器,成功返回0,錯誤返回-1
while (connect(sock_cli, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
{
perror("connect");
exit(1);
}
while(1){
/*把可讀文件描述符的集合清空*/
FD_ZERO(&rfds);
/*把標準輸入的文件描述符加入到集合中*/
FD_SET(0, &rfds);
maxfd = 0;
/*把當前連接的文件描述符加入到集合中*/
FD_SET(sock_cli, &rfds);
/*找出文件描述符集合中最大的文件描述符*/
if(maxfd < sock_cli)
maxfd = sock_cli;
/*設置超時時間*/
tv.tv_sec = 5;
tv.tv_usec = 0;
/*等待聊天*/
retval = select(maxfd+1, &rfds, NULL, NULL, &tv);
if(retval == -1){
printf("select出錯,客戶端程序退出\n");
break;
}else if(retval == 0){
printf("客戶端沒有任何輸入信息,並且服務器也沒有信息到來,waiting...\n");
continue;
}else{
/*服務器發來了消息*/
if(FD_ISSET(sock_cli,&rfds)){
char recvbuf[BUFFER_SIZE];
int len;
len = recv(sock_cli, recvbuf, sizeof(recvbuf),0);
printf("%s", recvbuf);
memset(recvbuf, 0, sizeof(recvbuf));
}
/*用戶輸入信息了,開始處理信息併發送*/
if(FD_ISSET(0, &rfds)){
char sendbuf[BUFFER_SIZE];
fgets(sendbuf, sizeof(sendbuf), stdin);
send(sock_cli, sendbuf, strlen(sendbuf),0); //發送
memset(sendbuf, 0, sizeof(sendbuf));
}
}
}
close(sock_cli);
return 0;
}
UDP網絡通信:
UDP 是User Datagram Protocol的簡稱, 中文名是用戶數據報協議,是OSI(Open System Interconnection,開放式系統互聯) 參考模型中一種無連接的傳輸層協議,提供面向事務的簡單不可靠信息傳送服務,IETF RFC 768是UDP的正式規範。UDP在IP報文的協議號是17。使用UDP可進行 單播 、廣播 、組播。
UDP網絡通信流程圖:
示例代碼服務端
#include <stdio.h>
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#define SERV_PORT 8000
int main()
{
/* sock_fd --- socket文件描述符 創建udp套接字*/
int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
if(sock_fd < 0)
{
perror("socket");
exit(1);
}
/* 將套接字和IP、端口綁定 */
struct sockaddr_in addr_serv;
int len;
memset(&addr_serv, 0, sizeof(struct sockaddr_in)); //每個字節都用0填充
addr_serv.sin_family = AF_INET;//使用IPV4地址
addr_serv.sin_port = htons(SERV_PORT);//端口
/* INADDR_ANY表示不管是哪個網卡接收到數據,只要目的端口是SERV_PORT,就會被該應用程序接收到 */
addr_serv.sin_addr.s_addr = htonl(INADDR_ANY); //自動獲取IP地址
len = sizeof(addr_serv);
/* 綁定socket */
if(bind(sock_fd, (struct sockaddr *)&addr_serv, sizeof(addr_serv)) < 0)
{
perror("bind error:");
exit(1);
}
int recv_num;
int send_num;
char send_buf[20] = "i am server!";
char recv_buf[20];
struct sockaddr_in addr_client;
while(1)
{
printf("server wait:\n");
recv_num = recvfrom(sock_fd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *)&addr_client, (socklen_t *)&len);
if(recv_num < 0)
{
perror("recvfrom error:");
exit(1);
}
recv_buf[recv_num] = '\0';
printf("server receive %d bytes: %s\n", recv_num, recv_buf);
send_num = sendto(sock_fd, send_buf, recv_num, 0, (struct sockaddr *)&addr_client, len);
if(send_num < 0)
{
perror("sendto error:");
exit(1);
}
}
close(sock_fd);
return 0;
}
客戶端
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <iostream>
#define DEST_PORT 8000
#define DSET_IP_ADDRESS "127.0.0.1"
int main()
{
/* socket文件描述符 */
int sock_fd;
/* 建立udp socket */
sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
if(sock_fd < 0)
{
perror("socket");
exit(1);
}
/* 設置address */
struct sockaddr_in addr_serv;
int len;
memset(&addr_serv, 0, sizeof(addr_serv));
addr_serv.sin_family = AF_INET;
addr_serv.sin_addr.s_addr = inet_addr(DSET_IP_ADDRESS);
addr_serv.sin_port = htons(DEST_PORT);
len = sizeof(addr_serv);
int send_num;
int recv_num;
char send_buf[20] = "hey, who are you?";
char recv_buf[20];
printf("client send: %s\n", send_buf);
send_num = sendto(sock_fd, send_buf, strlen(send_buf), 0, (struct sockaddr *)&addr_serv, len);
if(send_num < 0)
{
perror("sendto error:");
exit(1);
}
recv_num = recvfrom(sock_fd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *)&addr_serv, (socklen_t *)&len);
if(recv_num < 0)
{
perror("recvfrom error:");
exit(1);
}
recv_buf[recv_num] = '\0';
printf("client receive %d bytes: %s\n", recv_num, recv_buf);
close(sock_fd);
return 0;
}
關於UDP廣播組播相關示例待續...
參考: