回顧:
OSI七層模型
應用層
表示層
會話層
傳輸層
網絡層
數據鏈路層
物理層
TCP/IP四層模型
IP地址
A 0 + 7 + 24
B 10 + 14 + 16
C 110 + 21 + 8
D 1110 + 28位組播地址
E 留待備用
192.168.0.103 /24
127.0.0.1:迴環地址,可用於本機網絡協議的測試和本地進程間通信
端口號
16位無符號整數,0-65535 1024被系統使用
字節序
---------------------------------
socket編程:
1.網絡編程又叫socket編程
套接字是一個網絡編程的接口,是網絡數據傳輸的軟設備,用於網絡交互。
網絡編程就是編寫程序使兩臺連網的計算機相互交換數據,這個就是網絡編程的全部內容。
unix/linux系統作爲服務器操作系統存在至今,因此,Linux的網絡功能應該是非常全面和強大。
網絡編程其實有很成熟的通信模型,並且windows也通用。
2.通信模型(基於TCP的一對一的通信模型)
服務器:
1.創建socket,使用函數socket()
2.準備通信地址,使用結構體類型
3.綁定socket和通信地址,使用bind()
4.監聽,使用函數listen()
5.等待連接,使用函數accept()
6.進行通信,使用read()、write()
7.關閉socket,使用函數close()
客戶端:
1.創建socket,使用函數socket()
2.準備通信地址(指服務器的地址),使用結構體類型
3.連接服務器,使用函數connect()
4.進行通信,使用read()、write()
5.關閉socket,使用函數close()
具體函數:
1.socket()函數
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
功能:主要用於創建可以實現通信的交流點,也就是socket通信載體(相當於電話機)
第一個參數:域/協議族,決定了是本地通信還是網絡通信
AF_UNIX/AF_LOCAL 用於實現本地通信
AF_INET 用於實現基於IPv4的網絡通信
AF_INET6 用於實現基於IPv6的網絡通信
第二個參數:通信類型,決定了具體的通信式
SOCK_STREAM 提供有序的、可靠的、雙向的、面向連接的字節流通信方式,默認使用TCP協議
SOCK_DGRAM 提供無序的、不可靠的,非面向連接的數據報通信方式,默認使用UDP協議
第三個參數:指定具體的協議,默認爲0,使用默認協議
返回值:成功返回新的socket描述符
失敗返回-1,errno被設置
2.通信地址數據類型
1.通用地址結構
struct sockaddr
{
sa_family_t sa_family;
char sa_data[14];
};
2.IPv4通信地址結構
struct sockaddr_in
{
sa_family_t sin_family;//協議族,AF_INET
in_port_t sin_port;//16位的端口號
struct in_addr sin_addr;//IP地址
};
struct in_addr
{
in_addr_t s_addr;
};
例:
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr);
saddr.sin_family = AF_INET;
saddr.sin_port = htons(10086);
saddr.sin_addr.s_addr = inet_addr("192.168.0.103");
端口格式轉換相關函數:
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
h:host 本地格式
n:network 網絡格式(大端模式)
s:short 2字節整數
l:long 4字節整數
功能:把本地格式轉成網絡格式,或者反過來。
IP地址格式轉換相關函數:
in_addr_t inet_addr(const char *cp);
功能:將字符串形式的IP地址轉爲整數類型
char *inet_ntoa(struct in_addr in);
功能:將結構類型的IP地址轉爲字符串形式
3.綁定函數bind()
include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
功能:主要用於綁定socket和具體的通信地址
第一個參數:socket描述符,socket函數的返回值
第二個參數:結構體指針,不管是什麼協議的地址結構,都需要強轉爲該類型
第三個參數:通信地址結構的大小,使用sizeof()計算即可
返回值:成功返回0
失敗返回-1,errno被設置
4.監聽listen函數
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
功能:爲套接字sockfd建立一個連接請求監聽隊列,在調用listen函數成功後,這個套接字便成爲服務套接字,即被動套接字
第一個參數: socket描述符
第二個參數:監聽隊列的大小
返回值:成功返回0
失敗返回-1,errno被設置
5.等待連接,accept函數
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:用於接收服務套接字sockfd上的連接請求
第一個參數:socket描述符,socket函數的返回值
第二個參數:結構體指針,用於保存接受的客戶端通信地址
第三個參數:指針類型,用於保存客戶端網絡地址的信息的長度
返回值:成功返回一個連接套接字
失敗返回-1,errno被設置
注:如果對客戶地址不感興趣,那麼第二、三兩個參數寫NULL即可。
如果需要客戶的地址,那麼第三個參數必須由調用者初始化。
6.連接函數connect
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
功能:用於連接服務器
第一個參數:socket描述符,socket函數的返回值
第二個參數:服務器的通信地址
第三個參數:通信地址結構的大小,使用sizeof計算
返回值:成功返回0
失敗返回-1,errno被設置
迭代服務器
併發服務器
多進程服務器
多線程服務器
多路複用服務器
作業:
用多線程服務器實現羣聊
./a.out 192.168.0.129 10086 name
[name] hello
[name] hello
confd用數組保存即可
當有客戶連接,保存其fd
當有客戶下線,移除其fd
保存和移除的操作必須同步
-------------------------
IO多路複用
假如我們要在一個文件可讀的情況下,馬上讀到數據,我們可以在這個文件上阻塞的讀
如果有多個文件,那麼就需要多個進程/線程去阻塞的讀每個文件
能不能用一個函數,去實現多個文件的查詢是否就緒的功能?
select()
運用select()函數是最具有代表性的實現複用服務器的方法
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
參數:
nfds:感興趣的文件集合中最大的文件描述符 + 1;
readfds:對讀感興趣的文件描述符集合
writefds:對寫感興趣的文件描述符集合
exceptfds:對異常感興趣的文件描述符集合
timeout:超時時間
select實現原理:
select 在實現時,會輪詢,每隔一段時間就會去詢問文件描述符[0,nfds)中的每一個文件,查看是否就緒,如果就緒那麼把相應的文件描述符集合中置1.
...
直到有文件就緒或超時或出錯。
void FD_CLR(int fd, fd_set *set); //把指定fd從集合中移除
int FD_ISSET(int fd, fd_set *set); //判斷指定的fd是否在這個集合中
void FD_SET(int fd, fd_set *set); //把指定的fd設置到集合中去
void FD_ZERO(fd_set *set); //初始化指定的集合,全部清零
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
在調用select函數之前,readfds是你對讀感興趣的文件描述符集合
在調用select函數之後,readfds是你對讀感興趣並且可以讀的文件描述符集合
writefds,exceptfds同理。
timeout 在調用select之前,是超時時間
在調用select之後,是剩餘時間
select返回:
1.有文件數據就緒,立馬返回,返回就緒的文件描述符個數 >0
2.超時返回,返回0
3.出錯了,返回-1 ,errno被設置
select調用方法與順序:
1. 設置文件描述符
指定監控範圍
設置超時時間
2.調用select函數
3.查看調用結果
-----------------------------------------
1.TCP與UDP協議的比較:
1.tcp協議:
傳輸控制協議 類似打電話
面向連接的 (建立連接-->進行通信-->斷開連接)
在通信的整個過程中必需保持連接
該協議保證了數據的傳遞是可靠且有序的
屬於全雙工的字節流通信方式
服務器壓力較大,資源消耗大,執行效率較低
2.udp協議:
用戶數據報協議
面向非連接的 類似發短信
在通信的整個過程中不需要保持連接
不保證數據的可靠性和有序性
屬於全雙工的數據報通信方式
服務器壓力較小,資源消耗小,執行效率高
2.基於UDP協議的通信模型
服務器:
1.創建socket,使用socket()函數
2.準備通信地址,使用結構體類型
3.綁定socket和通信地址,使用bind函數
4.進行通信,使用sendto()/recvfrom()
5.關閉socket,使用函數close
客戶端:
1.創建socket,使用socket()函數
2.準備通信地址,使用結構體類型
3.進行通信,使用sendto()/recvfrom()
4.關閉socket,使用函數close()
相關API:
發送數據報函數:sendto()
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
第一個參數:socket描述符,socket函數的返回值
第二個參數:被髮送的數據首地址
第三個參數:發送數據的大小
第四個參數:發送標誌
0:功能與write一樣,即阻塞
MSG_DONTWAIT: 非阻塞
第五個參數:發送數據的目標地址
第六個參數:目標地址的大小
接收數據報的函數:recvfrom()
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
參數:參考sendto()函數
-----------------
UDP應用:
廣播
組播
套接選項:
我們正常使用套接字編程時,一般只關注數據通信,而忽略套接字的不同特性
但有時需要設置地址複用,允許發送廣播消息,將主機加入多播組,設置發送與接收緩衝區的大小等
這些都需要對套接字選項進行設置。
每個套接字在不同的協議層次上有不同的行爲屬性,那麼這個行爲屬性就稱爲套接字選項
getsockopt
setsockopt
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname,
void *optval, socklen_t *optlen);
注:optlen要初始化
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
第一個參數:socket描述符,socket函數的返回值
第二個參數:要設置的套接字選項所在的協議層次
第三個參數:套接字選項的名稱
第四個參數:保存要設置的選項值 的 緩衝區首地址
第五個參數:第四個參數的長度
SOL_SOCKET
SO_BROADCAST 允許發送廣播數據報 int
SO_REUSEADDR 允許重用本地地址 int
SO_RCVBUF 接收緩衝區大小 int
SO_SNDBUF 發送緩衝區大小 int
IPPROTO_IP
IP_ADD_MEMBERSHIP 加入多播組 ip_mreq{}
廣播:
廣播是向同一網絡中的所有主機傳輸數據的方法,是基於UDP的
廣播地址:
192.168.0.255 直接廣播/子網內廣播
255.255.255.255 本地廣播/全網廣播
多播/組播:
多播/組播的數據傳輸也是基於UDP的,是同時傳遞到加入特定多播組的大量主機。
多播組是D類IP地址(224.0.0.0~239.255.255.255)
加入多播組的方式:
struct ip_mreq
{
struct in_addr imr_multiaddr;//多播組的IP
struct in_addr imr_interface;//加入多播組的那臺主機的IP
};
struct ip_mreq join_addr;
join_addr.imr_multiaddr.s_addr = inet_addr("224.1.2.3");
join_addr.imr_interface.s_addr = inet_addr("192.168.1.110");
setsockopt(sockfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,(void*)&join_addr,sizeof(join_addr));
UNIX協議域 unix domain socket
AF_UNIX/
AF_LOCAL
#include <sys/un.h>
#define UNIX_PATH_MAX 108
struct sockaddr_un
{
sa_family_t sun_family; /* AF_UNIX */
char sun_path[UNIX_PATH_MAX]; /* pathname */
};
利用socket編程接口,來實現本地進程間通信
UDS提供兩類套接字:
字節流套接字(類似於TCP)
數據報套接字(類似於UDP)
練習:
利用網絡傳輸文件
1.文件名
2.文件大小 stat()
3.文件內容