网络编程之套接字
来源:互联网 发布:安装ubuntu没有win引导 编辑:程序博客网 时间:2024/05/17 16:01
看完了《TCP/IP网络编程》这本书,现在分批做点总结:
首先要理解什么是套接字:
百度百科的解释是这样的:
套接字,是支持TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。
非常非常简单的举例说明下:Socket=Ip address+ TCP/UDP + port。
在我看来,套接字就是网络编程的接口,是连接网络的一种工具。用IP地址区分主机、用套接字区分主机里面的程序、用端口号区分套接字
可分配的端口号是0-65535,但0-1023是知名端口号,不能使用;TCP套接字和UDP套接字可以共用同一个端口号
网络编程中接受连接请求的套接字创建过程可整理如下:
Linux:
首先介绍一下Linux相关知识:
1、底层文件访问(Low-Level File Access)和文件描述符
底层;与标准无关的操作系统独立提供的
文件描述符(对应于window的句柄):系统分配给文件或套接字的整数
文件和套接字一般经过创建过程才会被分配文件描述符,输入输出对象即使未经过特殊的创建过程,程序开始运行后也会被自动分配文件描述符。
2、打开文件的函数:
int open(const char *path, int flag);
成功时返回文件描述符,失败时返回-1.
path为文件名的字符串地址(路径信息),flag文件打开模式信息
3、关闭文件的函数
ssize_t write(int fd, const void * buf, size_t nbytes);
成功时返回写入的字节数,失败时返回-1
fd,显示数据传输对象的文件描述符
buf,保存要传输数据的缓冲地址值
nbytes,要传输数据的字节数
size_t是通过typedef声明的unsigned int类型,ssize_t代表signed int,
s代表signed
ssize_t、size_t都是元数据类型,操作体统定义的数据类型会添加后缀_t
4、创建文件的函数
fd=open("data.txt",O_CREAT|O_WRONLY|O_TRUNC);
创建空文件,只写模式打开,若存在同名文件,则清空文件全部数据
5、读取文件的函数
ssize_t read(int fd, void * buf, size_t nbytes);
成功时返回接收的字节数(文件结尾返回0),失败时返回-1
文件描述符从3开始以由小到大的顺序编号,0,1,2是分配给标准I/O的描述符
底层;与标准无关的操作系统独立提供的
文件描述符(对应于window的句柄):系统分配给文件或套接字的整数
文件和套接字一般经过创建过程才会被分配文件描述符,输入输出对象即使未经过特殊的创建过程,程序开始运行后也会被自动分配文件描述符。
2、打开文件的函数:
int open(const char *path, int flag);
成功时返回文件描述符,失败时返回-1.
path为文件名的字符串地址(路径信息),flag文件打开模式信息
3、关闭文件的函数
ssize_t write(int fd, const void * buf, size_t nbytes);
成功时返回写入的字节数,失败时返回-1
fd,显示数据传输对象的文件描述符
buf,保存要传输数据的缓冲地址值
nbytes,要传输数据的字节数
size_t是通过typedef声明的unsigned int类型,ssize_t代表signed int,
s代表signed
ssize_t、size_t都是元数据类型,操作体统定义的数据类型会添加后缀_t
4、创建文件的函数
fd=open("data.txt",O_CREAT|O_WRONLY|O_TRUNC);
创建空文件,只写模式打开,若存在同名文件,则清空文件全部数据
5、读取文件的函数
ssize_t read(int fd, void * buf, size_t nbytes);
成功时返回接收的字节数(文件结尾返回0),失败时返回-1
文件描述符从3开始以由小到大的顺序编号,0,1,2是分配给标准I/O的描述符
服务器端:
1、socket函数创建套接字
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
成功时返回文件描述符,失败时返回-1.
1、socket函数创建套接字
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
成功时返回文件描述符,失败时返回-1.
domain,套接字中使用的协议族信息;type,套接字数据传输类型信息;protocol,计算机间通信中使用的协议信息
- 协议族(Protocol Family):
PF_INET6,IPV6互联网协议族
PF_LOCAL,本地通信的Unix协议族
PF_PACKET,底层套接字的协议族
PF_IPX,IPX Novell协议族
套接字实际采用的最终协议信息是通过socket函数的第三个函数传递,
在指定协议族范围内通过第一个参数决定第三个参数
套接字类型(Type)
即套接字的数据传输方式,socket第二个参数传递
协议族中可能存在多种数据传输方式
特点:传输过程中数据不会丢失、按序传输数据、传输的数据不存在数据边界
套接字连接必须一一对应
收发数据的套接字内部有缓冲(buffer),即字节数组
面向连接的套接字会根据接收端的状态传输数据
int tcp_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
不可靠、不按顺序、无连接、有数据边界、以高速为目的
限制每次传输的数据大小
int udp_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
除非同一协议族中存在多个数据传输方式相同的协议
即套接字的数据传输方式,socket第二个参数传递
协议族中可能存在多种数据传输方式
- 面向连接的套接字(基于字节)
特点:传输过程中数据不会丢失、按序传输数据、传输的数据不存在数据边界
套接字连接必须一一对应
收发数据的套接字内部有缓冲(buffer),即字节数组
面向连接的套接字会根据接收端的状态传输数据
int tcp_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
- 面向消息的套接字
不可靠、不按顺序、无连接、有数据边界、以高速为目的
限制每次传输的数据大小
int udp_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
- 协议的最终选择
除非同一协议族中存在多个数据传输方式相同的协议
2、bind函数分配地址信息(IP地址和端口号)
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *myaddr,
socklen_t addrlen);
成功返回0,失败返回-1。
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *myaddr,
socklen_t addrlen);
成功返回0,失败返回-1。
sockfd 要分配地址信息(IP地址和端口号)的套接字文件描述符
myaddr 存有地址信息的结构体变量地址值
adddrien 第二个结构体变量的长度
myaddr 存有地址信息的结构体变量地址值
adddrien 第二个结构体变量的长度
地址信息的表示
AF_INET,IPV4网络协议中使用的地址族
AF_INET6,IPV6网络协议中使用的地址族
{
sa_family_t sin_family;//地址族
uint16_t sin_port;//16位tcp/udp端口号,以网络字节序保存
struct in_addr sin_addr;//32位IP地址,以网络字节序保存
char sin_zero[8];//不使用,必需为0,
为使结构体sockaddr_in的大小与sockaddr结构体保持一致而插入的成员
};
struct in_addr
{
in_addr_t s_addr;//32位IPV4地址
};
POSIX,UNIX系列操作系统的可移植操作系统接口,定义了一些其他数据类型
sa_family_t 地址族
socklen_t 长度
in_addr_t IP地址,声明为uint32_t
in_port_t 端口号,声明为uint16_t
字节序和网络字节序:
大端序:高位字节存放到低位地址 【高位字节值存放到低位地址,先存高位值】
小端序:高位字节存放到高位地址
IntelCPU系列用小端
网络字节序统一为大端序
字节序转换:
unsigned short htons(unsigned short);
unsigned short ntohs(unsigned short);
unsigned long htonl(unsigned long);
unsigned long ntohl(unsigned long);
h代表主机,n代表网络,s指short,l指long,to就是to
除了向sockaddr_in结构体变量填充数据外,其他情况无需考虑字节序问题
#include <arpa/inet.h>
in_addr_t inet_addr(const char * string);
成功返回 32位大端序整数型值,失败时返回INADDR_NONE
还可以检测无效IP
#include <arpa/inet.h>
int inet_aton(const char * string, struct in_addr * addr);
成功返回1,失败返回0
string 含有需转换的IP地址信息的字符串地址值
addr 将保存转换结果的in_addr结构体变量的地址值
aton和addr函数功能完全相同,利用了in_addr结构体,使用频率更高,
Windows没有此函数
网络地址初始化【主要针对服务器】
struct sockaddr_in addr;
char * serv_ip = "211.217.168.13"; //声明IP地址字符串
char * serv_port = "9190"; //声明端口号字符串
memset(&addr, 0, sizeof(addr)); //结构体变量addr的所有成员置零
addr.sin_family = AF_INET; //指定地址族
addr.sin_addr.s_addr = inet_addr(serv_ip); //基于字符串的IP地址初始化
addr.sin_port = htons(atoi(serv_port)); //基于字符串的端口号初始化
利用字符串格式的IP地址和端口号初始化了sockaddr_in结构体变量
客户端地址信息初始化
声明sockaddr_in结构体,初始化为要与之连接的服务器端套接字的IP和端口号,
再调用connect
INADDR_ANY
addr.sin_addr.s_addr = htonl(INADDR_ANY);
自动获取运行服务器端的计算机IP地址
同一计算机可以分配多个IP地址,个数与NIC相等
3、listen函数设置监听【将套接字转化为可接收请求状态】
#include <sys/socket.h>
int listen(int sockfd, int backlog);
成功返回0,失败返回-1
AF_INET,IPV4网络协议中使用的地址族
AF_INET6,IPV6网络协议中使用的地址族
- 相关结构体
{
sa_family_t sin_family;//地址族
uint16_t sin_port;//16位tcp/udp端口号,以网络字节序保存
struct in_addr sin_addr;//32位IP地址,以网络字节序保存
char sin_zero[8];//不使用,必需为0,
为使结构体sockaddr_in的大小与sockaddr结构体保持一致而插入的成员
};
struct in_addr
{
in_addr_t s_addr;//32位IPV4地址
};
POSIX,UNIX系列操作系统的可移植操作系统接口,定义了一些其他数据类型
sa_family_t 地址族
socklen_t 长度
in_addr_t IP地址,声明为uint32_t
in_port_t 端口号,声明为uint16_t
- 网络字节序与地址变换
字节序和网络字节序:
大端序:高位字节存放到低位地址 【高位字节值存放到低位地址,先存高位值】
小端序:高位字节存放到高位地址
IntelCPU系列用小端
网络字节序统一为大端序
字节序转换:
unsigned short htons(unsigned short);
unsigned short ntohs(unsigned short);
unsigned long htonl(unsigned long);
unsigned long ntohl(unsigned long);
h代表主机,n代表网络,s指short,l指long,to就是to
除了向sockaddr_in结构体变量填充数据外,其他情况无需考虑字节序问题
- 网络地址的初始化和分配
#include <arpa/inet.h>
in_addr_t inet_addr(const char * string);
成功返回 32位大端序整数型值,失败时返回INADDR_NONE
还可以检测无效IP
#include <arpa/inet.h>
int inet_aton(const char * string, struct in_addr * addr);
成功返回1,失败返回0
string 含有需转换的IP地址信息的字符串地址值
addr 将保存转换结果的in_addr结构体变量的地址值
aton和addr函数功能完全相同,利用了in_addr结构体,使用频率更高,
Windows没有此函数
网络地址初始化【主要针对服务器】
struct sockaddr_in addr;
char * serv_ip = "211.217.168.13"; //声明IP地址字符串
char * serv_port = "9190"; //声明端口号字符串
memset(&addr, 0, sizeof(addr)); //结构体变量addr的所有成员置零
addr.sin_family = AF_INET; //指定地址族
addr.sin_addr.s_addr = inet_addr(serv_ip); //基于字符串的IP地址初始化
addr.sin_port = htons(atoi(serv_port)); //基于字符串的端口号初始化
利用字符串格式的IP地址和端口号初始化了sockaddr_in结构体变量
客户端地址信息初始化
声明sockaddr_in结构体,初始化为要与之连接的服务器端套接字的IP和端口号,
再调用connect
INADDR_ANY
addr.sin_addr.s_addr = htonl(INADDR_ANY);
自动获取运行服务器端的计算机IP地址
同一计算机可以分配多个IP地址,个数与NIC相等
3、listen函数设置监听【将套接字转化为可接收请求状态】
#include <sys/socket.h>
int listen(int sockfd, int backlog);
成功返回0,失败返回-1
sock 希望进入等待连接请求状态的套接字文件描述符
传递的描述符套接字参数成为服务器端套接字(监听套接字)
backlog 连接请求等待队列(Queue)的长度,若为5,则队列长度为5 ,表示最多使5个连接请求进入队列
4、accept函数受理连接请求
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr,
socklen_t *addrlen);
成功时返回文件描述符,失败时返回-1.
传递的描述符套接字参数成为服务器端套接字(监听套接字)
backlog 连接请求等待队列(Queue)的长度,若为5,则队列长度为5 ,表示最多使5个连接请求进入队列
4、accept函数受理连接请求
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr,
socklen_t *addrlen);
成功时返回文件描述符,失败时返回-1.
sock 服务器套接字的文件描述符
addr 保存发起连接请求的客户端地址信息的变量地址值
调用函数后向传递来的地址变量参数填充客户端地址信息
addrlen 第二个参数addr结构体的长度,但是存有长度的变量地址。
函数调用完成后,该变量即被填入客户端地址长度
函数调用成功时,accept函数内部将产生用于数据I/O的套接字
并返回其文件描述符,套接字自动创建并自动与发起连接的客户端建立连接
addr 保存发起连接请求的客户端地址信息的变量地址值
调用函数后向传递来的地址变量参数填充客户端地址信息
addrlen 第二个参数addr结构体的长度,但是存有长度的变量地址。
函数调用完成后,该变量即被填入客户端地址长度
函数调用成功时,accept函数内部将产生用于数据I/O的套接字
并返回其文件描述符,套接字自动创建并自动与发起连接的客户端建立连接
客户端:
#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, socklen_t addrlen);
成功返回0,失败返回-1。
sock 客户端套接字的文件描述符
servaddr 保存目标服务器端地址信息的变量地址值
addrlen 以字节为单位传递已传递给第二个结构体参数
servaddr的地址变量长度
客户端调用connect函数后,服务器端接收连接请求或中断连接请求才会返回
客户端的IP地址和端口在调用connect函数时自动分配
int connect(int sockfd, struct sockaddr *serv_addr, socklen_t addrlen);
成功返回0,失败返回-1。
sock 客户端套接字的文件描述符
servaddr 保存目标服务器端地址信息的变量地址值
addrlen 以字节为单位传递已传递给第二个结构体参数
servaddr的地址变量长度
客户端调用connect函数后,服务器端接收连接请求或中断连接请求才会返回
客户端的IP地址和端口在调用connect函数时自动分配
以下是一份简单的代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
void error_handling(char *message);
int main(int argc, char* argv[])
{
int sock;
struct sockaddr_in serv_addr;
char message[30];
int str_len;
if(argc!=3)
{
printf("Usage : %s <IP> <port>\n", argv[0]);
exit(1);
}
sock=socket(PF_INET, SOCK_STREAM, 0); //建立TCP套接字
if(sock == -1)
error_handling("socket() error");
memset(&serv_addr, 0, sizeof(serv_addr)); //地址信息初始化
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=inet_addr(argv[1]);
serv_addr.sin_port=htons(atoi(argv[2]));
if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1)//请求连接
error_handling("connect() error!");
str_len=read(sock, message, sizeof(message)-1); //读取数据
if(str_len==-1)
error_handling("read() error!");
printf("Message from server : %s \n", message);
close(sock); //关闭套接字
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
void error_handling(char *message);
int main(int argc, char* argv[])
{
int sock;
struct sockaddr_in serv_addr;
char message[30];
int str_len;
if(argc!=3)
{
printf("Usage : %s <IP> <port>\n", argv[0]);
exit(1);
}
sock=socket(PF_INET, SOCK_STREAM, 0); //建立TCP套接字
if(sock == -1)
error_handling("socket() error");
memset(&serv_addr, 0, sizeof(serv_addr)); //地址信息初始化
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=inet_addr(argv[1]);
serv_addr.sin_port=htons(atoi(argv[2]));
if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1)//请求连接
error_handling("connect() error!");
str_len=read(sock, message, sizeof(message)-1); //读取数据
if(str_len==-1)
error_handling("read() error!");
printf("Message from server : %s \n", message);
close(sock); //关闭套接字
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
Windows:
1、winsock编程时,首选必须调用WSAStartup函数
#include <winsock2.h>
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
成功时返回0,失败时返回非零的错误代码值
wVersionRequested,程序员要用的Winsock版本信息
lpWSAData,WSADATA结构体变量的地址值
套接字版本信息应该准备WORD(unsigned short)类型的,传递给该函数第一个参数wVersionRequested,版本号为1.2,则传递0x0201
可用MAKEWORD宏函数构建WORD型版本信息
MAKEWORD(1, 2);版本为1.2;
2、注销库
#include <winsock2.h>
int WSACleanup(void);
成功时返回0,失败时返回SOCKET_ERROR
调用该函数,winsock相关库将归还给Windows操作系统,多在程序结束之前调用
3、相关函数
创建套接字
#include <winsock2.h>
SOCKET socket(int af, int type, int protocol);
成功时返回套接字句柄,失败时返回INVALID_SOCKET
分配IP和端口
int bind(SOCKET s, const struct sockaddr * name, int namelen);
成功时返回0,失败返回SOCKET_ERROR
激活套接字,使可接收客户端连接
int listen(SOCKET s,int backlog);
成功时返回0,失败返回SOCKET_ERROR
调用受理客户端连接请求
SOCKET accept(SOCKET s,struct sockaddr * addr, int * addrlen);
成功时返回套接字句柄,失败时返回INVALID_SOCKET
客户端发送连接请求
connect(SOCKET s, const struct sockaddr * name,int namelen);
关闭套接字
int closesocket(SOCKET s);
成功返回0,失败返回SOCKET_ERROR
不同于Linux,Windows的文件句柄相关函数和套接字句柄相关函数是有区别的
4、基于Windows的I/O函数
Windows严格区分套接字I/O函数和文件I/O函数
Windows数据传输函数
int send(SOCKET s, const char * buf, int len, int flags);
成功返回传输字节数,失败返回SOCKET_ERROR
s,数据传输对象连接的套接字句柄值
buf,保存待传输数据的缓冲地址值
len,要传输的字节数
flag,传输数据时用到的多种选项信息
int recv(SOCKET s,const char * buf,int len,int flags);
成功返回接收的字节数(收到EOF返回0),失败返回SOCKET_ERROR
#include <winsock2.h>
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
成功时返回0,失败时返回非零的错误代码值
wVersionRequested,程序员要用的Winsock版本信息
lpWSAData,WSADATA结构体变量的地址值
套接字版本信息应该准备WORD(unsigned short)类型的,传递给该函数第一个参数wVersionRequested,版本号为1.2,则传递0x0201
可用MAKEWORD宏函数构建WORD型版本信息
MAKEWORD(1, 2);版本为1.2;
2、注销库
#include <winsock2.h>
int WSACleanup(void);
成功时返回0,失败时返回SOCKET_ERROR
调用该函数,winsock相关库将归还给Windows操作系统,多在程序结束之前调用
3、相关函数
创建套接字
#include <winsock2.h>
SOCKET socket(int af, int type, int protocol);
成功时返回套接字句柄,失败时返回INVALID_SOCKET
分配IP和端口
int bind(SOCKET s, const struct sockaddr * name, int namelen);
成功时返回0,失败返回SOCKET_ERROR
激活套接字,使可接收客户端连接
int listen(SOCKET s,int backlog);
成功时返回0,失败返回SOCKET_ERROR
调用受理客户端连接请求
SOCKET accept(SOCKET s,struct sockaddr * addr, int * addrlen);
成功时返回套接字句柄,失败时返回INVALID_SOCKET
客户端发送连接请求
connect(SOCKET s, const struct sockaddr * name,int namelen);
关闭套接字
int closesocket(SOCKET s);
成功返回0,失败返回SOCKET_ERROR
不同于Linux,Windows的文件句柄相关函数和套接字句柄相关函数是有区别的
4、基于Windows的I/O函数
Windows严格区分套接字I/O函数和文件I/O函数
Windows数据传输函数
int send(SOCKET s, const char * buf, int len, int flags);
成功返回传输字节数,失败返回SOCKET_ERROR
s,数据传输对象连接的套接字句柄值
buf,保存待传输数据的缓冲地址值
len,要传输的字节数
flag,传输数据时用到的多种选项信息
int recv(SOCKET s,const char * buf,int len,int flags);
成功返回接收的字节数(收到EOF返回0),失败返回SOCKET_ERROR
以下是Windows的代码:
#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
void ErrorHandling(char* message);
int main(int argc, char *argv[]) {
WSADATA wsaData;
SOCKET hSocket;
SOCKADDR_IN servAddr;
char message[30];
int strlen;
if (argc != 3) {
printf("Usage: %s <IP> <port>\n", argv[0]);
exit(1);
}
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
ErrorHandling("WSAStartup() error!");
hSocket = socket(PF_INET, SOCK_STREAM, 0);
if (hSocket == INVALID_SOCKET)
ErrorHandling("socket() error!");
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_addr.s_addr = inet_addr(argv[1]);
servAddr.sin_port = htons(atoi(argv[2]));
if (connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
ErrorHandling("connect() error!");
strlen = recv(hSocket, message, sizeof(message) - 1, 0);
if (strlen == -1)
ErrorHandling("read() error!");
printf("Message from server:%s\n", message);
closesocket(hSocket);
WSACleanup();
return 0;
}
void ErrorHandling(char * message) {
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
#include <stdlib.h>
#include <WinSock2.h>
void ErrorHandling(char* message);
int main(int argc, char *argv[]) {
WSADATA wsaData;
SOCKET hSocket;
SOCKADDR_IN servAddr;
char message[30];
int strlen;
if (argc != 3) {
printf("Usage: %s <IP> <port>\n", argv[0]);
exit(1);
}
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
ErrorHandling("WSAStartup() error!");
hSocket = socket(PF_INET, SOCK_STREAM, 0);
if (hSocket == INVALID_SOCKET)
ErrorHandling("socket() error!");
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_addr.s_addr = inet_addr(argv[1]);
servAddr.sin_port = htons(atoi(argv[2]));
if (connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
ErrorHandling("connect() error!");
strlen = recv(hSocket, message, sizeof(message) - 1, 0);
if (strlen == -1)
ErrorHandling("read() error!");
printf("Message from server:%s\n", message);
closesocket(hSocket);
WSACleanup();
return 0;
}
void ErrorHandling(char * message) {
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
阅读全文