在介紹套接字前,我們需要了解什麼是IP地址,什麼是端口號port:
1.IP地址
ip協議有兩個版本,IPv4 和 IPv6 (一般情況下使用IPv4);
IPv4協議佔32個bit;IPv6佔128個bit是IPv4的4倍;
1) IP地址是在IP協議中用來標識網絡中不同主機的地址;
2) 對於IPv4來說,IP地址是一個4字節,32位的整數
3) 我們通常也是用“ 點分十進制 ”的字符串表示IP地址,例如 192.168.0.1 ;用點分割的每一個數字表示一個字節,範圍是 0 ~ 255;
2.端口號
端口號是傳輸層協議的內容;
1)端口號是一個2字節16位的整數
2)端口號用來標識一個進程,告訴操作系統,當前的這個數據要交給哪一個進程來處理;
3)IP地址+端口號 能夠標識網絡上的某一臺主機的某一個進程
4)一個端口號只能被一個進程佔用
(注:一個進程可以綁多個端口號,但一個端口號只能被一個進程綁定;)
一、套接字簡單介紹
1.什麼是套接字
在Unix/Linux中,一切皆文件。那對於這兩個操作系統而言,“端點”就是一個特殊的文件,也就是說Socket實際上就是文件。既然Socket是文件,那就可以用“打開open –> 讀寫write/read –> 關閉close”模式來操作它,一些socket函數就是對其進行的操作(讀/寫IO、打開、關閉)
2.對於一個Socket而言,它至少需要3個參數來指定:
1)通信的目的地址;
2)使用的傳輸層協議(如TCP、UDP);
3)使用的端口號。
3.套接字類型是指創建套接字的應用程序要使用的通信服務類型。linux系統支持多種套接字類型,最常用的有以下三種:
1)SOCK_STREAM:流式套接字,提供面向連接、可靠的數據傳輸服務,數據按字節流、按順序收發,保證在傳輸過程中無丟失、無冗餘。TCP協議支持該套接字。
2)SOCK_DGRAM:數據報套接字,提供面向無連接的服務,數據收發無序,不能保證數據的準確到達。UDP協議支持該套接字。
3)SOCK_RAW:原始套接字。允許對低於傳輸層的協議或物理網絡直接訪問,例如可以接收和發送ICMP報文。常用於檢測新的協議。
4.套接字(socket)編程接口
//創建socket文件描述符(TCP/UDP,客戶端 + 服務器)
int socket(int domain,int type,int protocol);
//綁定端口號(TCP/UDP,服務器)
int bind(int socket,const struct sockaddr* address,socklen_t address_len);
//監聽(TCP,服務器)
int listen(int socket,int bakclog);
//接受請求,生成新的套接字(TCP,服務器)
int accept(int socket,struct sockaddr* address,socklen_t addrlen);
//建立連接(TCP ,服務器)
int connect(int socket,const struct sockaddr* addr,socklen_t addrlen);
//sockaddr結構
struct sockaddr
{
_SOCKADDR_COMMON( sa_ );
char sa_data[14];
}
//sockaddr_in結構
struct sockaddr_in
{
_SOCKADDR_COMMON(sin_);
in_port_t sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[sizeof(struct sockaddr)-
_SOCKADDR_COMMON_SIZE-
sizeof(in_port)-
sizeof(struct in_addr)];
};
//在基於IPv4編程時,使用的數據結構是sockaddr_in;這個結構體裏主要有三部分信息:地址類型、端口號、IP地址
//in_addr結構
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
};
//in_addr用來表示一個Ipv4的Ip地址。就是一個32位的整數。
5.網絡協議中的字節序
所有的網絡協議爲了統一,全都使用的是大端存儲——>下面介紹幾個字節序轉換函數
#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(uint32_t netshort); //網絡序列轉換成主機序列
二、基於UDP協議編寫簡單的服務器
1.服務器中的主要操作
(1)創建套接字 int sock=socket( AF_INET , SOCK_DGRAM , 0); 參數SOCK_DGRAM表示UDP。
(2)綁定,bind之後就可以直接進行通信了。
(3)使用sendto和recvfrom來進行數據讀寫。
2.客戶端的主要操作
(1)創建套接字。
(2)使用sendto和recvfrom來進行數據讀寫。
3.幾個重要的函數
//字符串轉in_addr函數
#include<arpa/inet.h>
int inet_aton(const char*strptr,struct in_addr* addrptr);
in_addr_t inet_addr(const char* strptr);
int inet_pton(int family,const void* addrptr,char* strptr,size_t len);
//in_addr轉字符串函數
char* inet_ntoa(struct in_addr inaddr);
const char* inet_ntop(int family,const char* addrptr,void * strptr,size_t len);
//其中inet_pton和inet_ntop不僅可以轉換IPv4的addr,也可以轉換Ipv6的in6_addr,因此函數接口是void* addrptr 。
4.代碼實現
【server.c】
#include<stdio.h>
#include<arpa/inet.h>
#include<string.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/types.h>
int main(int argc,char* argv[])
{
int sock=socket(AF_INET,SOCK_DGRAM,0); //創建套接字
if(sock<0)
{
perror("socket");
return 2;
}
struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port=htons(atoi(argv[2]));
local.sin_addr.s_addr=inet_addr(argv[1]);
if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0) //綁定
{
perror("bind");
return 3;
}
char buf[1024];
struct sockaddr_in client;
while(1)
{
socklen_t len=sizeof(client);
size_t s=recvfrom(sock,buf,sizeof(buf)-1,0,\
(struct sockaddr*)&client,&len); //接收信息
if(s>0)
{
buf[s]=0;
printf("[%s:%d]:%s\n",inet_ntoa(client.sin_addr),\
ntohs(client.sin_port),buf);
sendto(sock,buf,strlen(buf),0,\
(struct sockaddr*)&client,sizeof(client)); //發送信息
}
}
return 0;
}
【client.c】
#include<stdio.h>
#include<arpa/inet.h>
#include<string.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<sys/types.h>
#include<sys/socket.h>
int main(int argc,char* argv[])
{
int sock=socket(AF_INET,SOCK_DGRAM,0); //創建套接字
if(sock<0)
{
perror("socket");
return 2;
}
struct sockaddr_in server;
server.sin_family=AF_INET;
server.sin_port=htons(atoi(argv[2]));
server.sin_addr.s_addr=inet_addr(argv[1]);
char buf[1024];
struct sockaddr_in peer;
while(1)
{
socklen_t len=sizeof(peer);
printf("Please Enter: ");
fflush(stdout);
size_t s=read(0,buf,sizeof(buf)-1);
if(s>0)
{
buf[s-1]=0;
sendto(sock,buf,strlen(buf),0,\
(struct sockaddr*)&server,sizeof(server)); //發送信息
size_t _s=recvfrom(sock,buf,sizeof(buf)-1,0,(struct sockaddr*)&peer,&len); //接收信息
if(_s>0)
{
buf[_s]=0;
printf("server echo# %s\n",buf);
}
}
}
return 0;
}
三、基於TCP協議編寫簡單的服務器
1.服務器的主要操作
(1)創建套接字 int sock=socket(AF_INET , SOCK_STREAM , 0); 參數SOCK_STREAM表示TCP
(2)綁定 bind
(3)監聽套接字狀態 listen
(4)accept監聽套接字,獲取新的文件描述符
(5)讀取信息,發送信息等功能的實現
2.客戶端的主要操作
(1)創建套接字 int sock=socket(AF_INET , SOCK_STREAM , 0); 參數SOCK_STREAM表示TCP
(2)連接到服務器 connect
(3)發送信息、接收信息功能的實現
3.幾個重要的函數
//(1)bind綁定函數
#include<sys/types.h>
#include<sys/socket.h>
int bind(int socket,const struct sockaddr* addr,socklen_t addrlen);
//成功返回0,失敗返回-1
//(2)listen監聽函數
#include<sys/types.h>
#include<sys/socket.h>
int listen(int socket,int backlog);
//成功返回0,失敗返回-1
//backlog 表示的是等待隊列的個數,由服務器維護,必須要有但不要太長
//(3)accept接收連接函數
#include<sys/types.h>
#include<sys/socket.h>
int accept(int socket,struct sockaddr* addr ,socklen_t* addrlen);
//addr是一個傳出參數
//返回值是新的進行操作的套接字
//(4)對myaddr參數的初始化
bzero(&servaddr,sizeof(servaddr)); //將整個結構清0
servaddr.sin_family=AF_INET; //設置地址類型位AF_INET
servaddr.sin_addr.s_addr=htonl(INADDR_ANY); //INADDR_ANY該宏表示本地的任意IP
servaddr.sin_port=htons(SERV_PORT); //SERV_PORT 表示端口號9999 ,
//(5)connect連接函數
#include<sys/types.h>
#include<sys/socket.h>
int connect(int socket,const struct sockaddr* addr,socklen_t addrlen);
//客戶端用來連接服務器
//connect與bind的參數形式一致,區別在於bind的參數是自己的地址,而connect的參數是對方的地址
//成功返回0,失敗返回-1;
4.代碼實現
【server.c】
#include<stdio.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<string.h>
#include<arpa/inet.h>
#define _PORT 9999 //端口號
#define _BACKLOG 10 //等待隊列個數
int main()
{
int sock=socket(AF_INET,SOCK_STREAM,0); //創建套接字
if(sock<0)
{
perror("socket!");
return 2;
}
struct sockaddr_in client_sock;
struct sockaddr_in server_sock;
bzero(&server_sock,sizeof(server_sock));
server_sock.sin_family=AF_INET;
server_sock.sin_addr.s_addr=htonl(INADDR_ANY);
server_sock.sin_port=htons(_PORT);
if(bind(sock,(struct sockaddr*)&server_sock,sizeof(struct sockaddr_in))<0) //綁定
{
perror("bind!");
close(sock);
return 3;
}
if(listen(sock,_BACKLOG)<0) //監聽
{
perror("listen!");
close(sock);
return 4;
}
printf("bind and listen success,wait accept...\n");
///////////////////////////////////////////
while(1)
{
socklen_t len=0;
int newsock=accept(sock,(struct sockaddr*)&client_sock,&len); //接收連接請求
if(newsock<0)
{
perror("accept!");
close(sock);
return 5;
}
char buf_ip[INET_ADDRSTRLEN];
memset(buf_ip,0,sizeof(buf_ip));
inet_ntop(AF_INET,&client_sock.sin_addr,buf_ip,sizeof(buf_ip));
printf("get connect,ip is:%s port is:%d\n",buf_ip,ntohs(client_sock.sin_port));
while(1)
{
char buf[1024];
int s=read(newsock,buf,sizeof(buf)-1); //接收信息
if(s>1)
{
buf[s]=0;
printf("client:# %s\n",buf);
write(newsock,buf,strlen(buf)); //發送信息
}
else if(s==0)
{
printf("client [ip:%s | %d] exit!\n",buf_ip,ntohs(client_sock.sin_port));
break;
}
}
}
close(sock); //關閉套接字(文件描述符)
return 0;
}
【client.c】
#include<stdio.h>
#include<errno.h>
#include<unistd.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
#include<signal.h>
#define SERVER_PORT 9999
//
int main(int argc,char*argv[])
{
if(argc!=2)
{
perror("usage");
return 1;
}
char*str=argv[1];
char buf[1024];
memset(buf,0,sizeof(buf));
int sock=socket(AF_INET,SOCK_STREAM,0); //創建套接字
if(sock<0)
{
perror("socket!");
return 2;
}
struct sockaddr_in server_sock; //
bzero(&server_sock,sizeof(server_sock));
server_sock.sin_family=AF_INET;
inet_pton(AF_INET,str,&server_sock.sin_addr);
server_sock.sin_port=htons(SERVER_PORT);
int ret=connect(sock,(struct sockaddr*)&server_sock,sizeof(server_sock)); //請求連接
printf("connect success...\n");
///////////////////////////////////////////
while(1)
{
printf("client :#");
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1]=0;
write(sock,buf,sizeof(buf));
if(strncasecmp(buf,"quit",4)==0)
{
printf("quit!\n");
break;
}
read(sock,buf,sizeof(buf));
printf("server :$ %s\n",buf);
}
close(sock);
return 0;
}