Windows和Linux的套接字基础

前言

主要用来记录Windows和Linux关于套接字的一些函数基础。

Linux的文件操作

在Linux下套接字的使用与文件使用保持一致。主要方法如下所示。

文件描述符

文件描述符对象
0标准输入:Standard Input
1标准输出:Standard Output
2标准错误:Standard Error

套接字的文件描述符从3开始以由小到大的顺序编号,因为0、1、2是分配给标准I/O的描述符。

打开文件

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *path,int flag);
//成功时返回文件描述符,失败时返回-1。
  • path:文件名的字符串地址。
  • flag:文件打开模式信息。
文件打开模式
打开模式含义
O_CREAT必要时创建文件
O_TRUNC删除全部现有数据
O_APPEND维持现有数据,保存到其后面
O_RDONLY只读打开
O_WRONLY只写打开
O_RDWR读写打开件

关闭文件

不仅可以关闭文件,也可以关闭套接字。

#include <unistd.h>
int close(int fd);
//成功时返回0,失败时返回-1.
  • fd:需要关闭的文件或套接字的文件描述符。
优雅断开套接字连接

使用close函数是单方面断开连接,这种完全断开不仅指无法传输数据,而且也不能接收数据。
在这里插入图片描述
如图所示,2台主机正在进行双向通信。主机A发送完最后的数据后,调用close函数断开了连接,之后主机A无法再接收主机B传输的数据。实际上,是完全无法调用与接收数据相关的函数。最终,由主机B传输的、主机A必须接收的数据也销毁了。
为了解决这类问题,“只关闭一部分数据交换中使用的流”(Half-close)的方法应运而生。断开一部分连接是指,可以传输数据但无法接收,或可以接收数据但无法传输。顾名思义就是只关闭流的一半。

#include <sys/socket.h>
int shutdown(int sock,int howto);
//成功时返回0,失败时返回-1。
  • sock:需要断开的套接字文件描述符。
  • howto:传递断开方式信息。

第二个参数决定断开连接方式:

  • SHUT_RD:断开输入流。
  • SHUT_WR:断开输出流。
  • SHUT_RDWR:同时断开I/O流。

若向shutdown的第二个参数传递SHUT_RD,则断开输人流,套接字无法接收数据。即使输人缓冲收到数据也会抹去,而且无法调用输入相关函数。如果向shutdown函数的第二个参数传递SHUT_WR,则中断输出流,也就无法传输数据。但如果输出缓冲还留有未传输的数据,则将传递至目标主机。最后,若传人SHUT_RDWR,则同时中断I/O流。这相当于分2次调用shutdown,其中一次以SHUT_RD为参数,另一次以SHUT_WR为参数。

将数据写入文件

该函数用于向文件输出(传输)数据,通过套接字向其他计算机传递数据时也会用到该函数。

#include <unistd.h>
ssize_t write(int fd,const void *buf,size_t nbytes);
//成功返回写入的字节数,失败时返回-1。
  • fd:显示数据传输对象的文件描述符。
  • buf:保存要传输数据的缓冲地址值。
  • nbytes:要传输数据的字节数。
    注:size_t是通过typedef声明的unsigned int类型,对ssize_t来说,size_t前面多加的s代表signed,即ssize_t是通过typedef声明的signed int类型。

读取文件中的数据

用于输入(接受)数据。

#include <unistd.h>
ssize_t read(int fd,const void *buf,size_t nbytes);
//成功返回接受的字节数(但遇到文件结尾则返回0),失败时返回-1。
  • fd:显示数据传输对象的文件描述符。
  • buf:保存要传输数据的缓冲地址值。
  • nbytes:要传输数据的字节数。

Windows套接字

在windows基础上开发网络程序,需要进行以下准备。

  • 导入头文件winsock2.h
  • 链接ws2_32.lib库

Winsock的初始化

进行Winsock编程,首先必须调用WSAStartup函数,设置程序中用的的Winsock版本,并初始化相应版本的库。

#include <winsock2.h>
int WSAStartup(WORD wVersionRequested,LPWSADATA lpWSAData);
//成功时返回0,失败时返回非零的错误代码值。
  • wVersionRequested:程序员要用的Winsock版本信息。
  • lpWSAData:WSADATA结构体变量的地址值。

对于wVersionRequested参数,其中Winsock中存在多个版本,应准备WORD类型的(WORD是通过typedef声明定义的unsigned short类型)套接字版本信息,我们借助MAKEWORD宏函数构建WORD型版本信息。

  • MAKEWORD(1,2); //主版本为1,副版本为2,返回0x0201。
  • MAKEWORD(2,2); //主版本为2,副版本为2,返回0x0202。
    对于lpWSAData参数,此参数中需传入WSADATA型结构体变量地址(LPWSADATA是WSADATA的指针类型)。调用完函数后,相应参数中将填充已初始化的库信息。

Winsock库的注销

将相关库归还给Windows操作系统,通常在程序结束之前调用。

#include <winsock2.h>
int WSACleanup(void);
//成功时返回0,失败时返回SOCKET_ERROR。

Windows套接字相关函数

统一头文件:#include <winsock2.h>

  • 与linux下的socket函数相同,创建socket套接字:
SOCKET socket(int af,int type,int protocol);
//成功时返回套接字句柄,失败时返回INVALID_SOCKET。
  • 与linux下的bind函数相同,调用其分配IP地址和端口号:
int bind(SOCKET s,const struct sockaddr * name,int namelen);
//成功时返回0,失败时返回SOCKET_ERROR。
  • 与linux下的listen函数相同,调用其使套接字可接受客户端连接:
int listen(SOCKET s,int backlog);
//成功时返回0,失败时返回SOCKET_ERROR。
  • 与linux下的accept函数相同,调用其受理客户端连接请求:
SOCKET accept(SOCKET s,struct sockaddr * addr,int * addrlen);
//成功时返回套接字句柄,失败时返回INVALID_SOCKET。
  • 与linux的connect函数相同,调用其从客户端发送连接请求:
int connect(SOCKET s,const struct sockaddr * addr,int * namelen);
//成功时返回0,失败时返回SOCKET_ERROR。
  • windows独有的关闭套接字函数,调用其关闭套接字:
int closesocket(SOCKET s);
//成功时返回0,失败时返回SOCKET_ERROR。

优雅断开套接字连接

#include <winsock2.h>
int shutdown(SOCKET sock,int howto);
//成功时返回0,失败时返回-1。
  • sock:需要断开的套接字文件描述符。
  • howto:传递断开方式信息。

第二个参数决定断开连接方式:

  • SHUT_RD:断开输入流。
  • SHUT_WR:断开输出流。
  • SHUT_RDWR:同时断开I/O流。

Hello world网络编程

创建套接字,但此时套接字并不马上分为服务器端和客户端,如果紧跟着调用bind、listen函数,将成为服务器端套接字,如果调用connect函数,将成为客户端套接字。

Windows平台

服务端

hello_server_win.c

#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>

void ErrorHandling(char *message);

int main(int argc,char *argv[])
{
	WSADATA wsaData;
	SOCKET hServSock,hClntSock;
	SOCKADDR_IN servAddr,clntAddr;
	
	int szClntAddr;
	char message[] = "hello world!";
	if(argc != 2)
	{
		printf("Usage : %s <port>\n",argv[0]);
		exit(1);
	}
	if(WAStartup(MAKEWORD(2,2),&wsaData) != 0)
		error_handling("WAStartup() error");
	
	hServSock = socket(PF_INET,SOCK_STREAM,0);
	if(hServSock  == INVALID_SOCKET)
		error_handling("socket() error");
	
	memset(&servAddr,0,sizeof(servAddr));
	servAddr.sin_family = AF_INET;
	servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servAddr.sin_port = htons(atoi(argv[1]));

	if(bind(hServSock,(SOCKADDR*)&servAddr,sizeof(servAddr)) == SOCKET_ERROR)
		error_handling("bind() error");
	if(listen(hServSock,5) == SOCKET_ERROR)
		error_handling("listen() error");
	
	szClntAddr = sizeof(clntAddr);
	hClntSock = accept(hServSock,(SOCKADDR*)&clntAddr,&szClntAddr);
	if(hClntSock == INVALID_SOCKET)
		error_handling("accept() error");
	
	send(hClntSock,message,sizeof(message),0);
	closesocket(hClntSock);
	closesocket(hServSock);
	WSACleanup();
	return 0;																																																						 
}

void error_handling(char *message)
{
	fputs(message,stderr);
	fputc('\n',stderr);
	exit(1);
}
客户端

hello_client_win.c

#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>

void error_handling(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 <port>\n",argv[0]);
		exit(1);
	}
	if(WAStartup(MAKEWORD(2,2),&wsaData) != 0)
		error_handling("WAStartup() error");
	
	hSocket = socket(PF_INET,SOCK_STREAM,0);
	if(hServSock  == INVALID_SOCKET)
		error_handling("socket() error");
	
	memset(&servAddr,0,sizeof(servAddr));
	servAddr.sin_family = AF_INET;
	servAddr.sin_addr.s_addr = htonl(argv[1]);
	servAddr.sin_port = htons(atoi(argv[2]));

	if(connect(hSocket,(SOCKADDR*)&servAddr,sizeof(servAddr)) == SOCKET_ERROR)
		error_handling("connect() error");
	
	strLen = resv(hSocket,message,sizeof(message)-1,0);
	if(strLen == -1)
		error_handling("read() error");
	printf("Message from server:%s\n",message); 
	
	closesocket(hSocket);
	WSACleanup();
	return 0;																																																						 
}

void error_handling(char *message)
{
	fputs(message,stderr);
	fputc('\n',stderr);
	exit(1);
}

Linux平台

服务端

hello_server.c

#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 serv_sock;
	int clnt_sock;
	
	struct sockaddr_in serv_addr;
	struct sockaddr_in clnt_addr;
	socklen_t clnt_addr_size;

	char message[] = "hello world!";
	
	if(argc != 2)
	{
		printf("Usage : %s <port>\n",argv[0]);
		exit(1);
	}

	serv_sock = socket(PF_INET,SOCK_STREAM,0);
	if(serv_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 = htonl(INADDR_ANY);
	serv_addr.sin_port = htons(atoi(argv[1]));

	if(bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr)) == -1)
		error_handling("bind() error");
	if(listen(serv_sock,5) == -1)
		error_handling("listen() error");
	
	clnt_addr_size = sizeof(clnt_addr);
	clnt_sock = accept(serv_sock,(struct sockaddr*)&clnt_addr,&clnt_addr_size);
	if(clnt_sock == -1)
		error_handling("accept() error");
	
	write(clnt_sock,message,sizeof(message));
	close(clnt_sock);
	close(serv_sock);
	return 0;																																																						 
}

void error_handling(char *message)
{
	fputs(message,stderr);
	fputc('\n',stderr);
	exit(1);
}
客户端

hello_client.c

#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 <port>\n",argv[0]);
		exit(1);
	}

	sock = socket(PF_INET,SOCK_STREAM,0);
	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);
}

IP地址与域名转换函数

Linux

利用域名获取IP地址
#include<netdb.h>
struct hostent * gethostbyname(const char * hostname);
//成功时返回hostent结构体地址,失败时返回NULL指针
//hostnt结构体
struct hostent{
	char * h_name;			//official name
	char ** h_aliases;		//alias list
	int h_addrtype;			//host address type
	int h_length;			//address length
	char ** h_addr_list;	// address list
}

在这里插入图片描述

利用IP地址获取域名
#include <netdb.h>
struct hostent* gethostbyaddr(const char * addr,socklen_t len,int family);
//成功时返回hostent结构体变量地址值,失败时返回NULL指针。
  • addr:含有IP地址信息的in_addr结构体指针。为了同时传递IPv4地址之外的其他信息,该变量的类型声明为char指针。
  • len:向第一个参数传递的地址信息的字节数,IPv4时为4,IPv6时为16。
  • family:传递地址族信息,IPv4时为AF_INET,IPv6时为AF_INET6。

Windows

利用域名获取IP地址
#include<netdb.h>
struct hostent * gethostbyname(const char * hostname);
//成功时返回hostent结构体地址,失败时返回NULL指针
//hostnt结构体
struct hostent{
	char * h_name;			//official name
	char ** h_aliases;		//alias list
	int h_addrtype;			//host address type
	int h_length;			//address length
	char ** h_addr_list;	// address list
}
利用IP地址获取域名
#include <netdb.h>
struct hostent* gethostbyaddr(const char * addr,socklen_t len,int family);
//成功时返回hostent结构体变量地址值,失败时返回NULL指针。

套接字的可选项

套接字具有多种特性,这些特性可通过可选项更改。

部分套接字可选项
协议层选项名读取设置
SOL_SOCKETSO_SNDBUFOO
SO_RCVBUFOO
SO_REUSEADDROO
SO_KEERALIVEOO
SO_BROADCASTOO
SO_DONTROUTEOO
SO_OOBINLINEOO
SO_ERROROX
SO_TYPEOX
IPPROTO_IPIP_TOSOO
IP_TTLOO
IP_MULTICAST_TTLOO
IP_MULTICAST_LOOPOO
IP_MULTICAST_IFOO
IPPROTO_TCPTCP_KEEPALIVEOO
TCP_NODELAYOO
TCP_MAXSEGOO

getsockopt & setsockopt

Linux

可选项的读取和设置通过如下2个函数完成。
读取:

#include <sys/socket.h>
int getsockopt(int sock,int level,int optname, void *optval, socklen_t *optlen);
//成功时返回0,失败时返回-1。
  • sock:用于查看选项套接字文件描述符。
  • level:要查看的可选项的协议层。
  • optname:要查看的可选项名。
  • optval:保存查看结果的缓冲地址值。
  • optlen:向第四个参数optval传递的缓冲大小。调用函数后,该变量中保存通过第四个参数返回的可选项信息的字节数。
#include <sys/socket.h>
int setsockopt(int sock,int level, int optname, const void *optval, socklen_t optlen);
//成功时返回0,失败时返回-1。
  • sock:用于更改可选项的套接字文件描述符。
  • level:要更改的可选项协议层。
  • optname:要更改的可选项名。
  • optval:保存要更改的选项信息的缓冲地址值。
  • optlen:向第四个参数optval传递的可选项信息的字节数。
Windows
#include <winsock2.h>
int getsockopt(SOCKET sod&,. int level,int optname,char * optval,int * optlen);
//成功时返回0,失败时返回SOCKET_ERROR。
  • sock:要查看可选项的套接字句柄。
  • level:要查看的可选项协议层。
  • optname:要查看的可选项名。
  • optval:保存查看结果的缓冲地址值。
  • optlen:向第四个参数optval传递的缓冲大小。调用结束后,该变量中保存通过第四个参数返回的可选项字节数。

除了optval类型变成char指针外,与Linux中的getsockopt函数相比并无太大区别( Linux中是void型指针)。将Linux中的示例移植到Windows时,应做适当的类型转换。接下来给出setsockopt函数。

#include <winsock2.h>
int setsockopt(SOCKET sock, int level, int optname,const char* optval,int optlen);
//成功时返回0,失败时返回SOCKET__ERROR。
  • sock:要更改可选项的套接字句柄。
  • level:要更改的可选项协议层。
  • optname:要更改的可选项名。
  • optval:保存要更改的可选项信息的缓冲地址值。
  • optlen:传入第四个参数optval的可选项信息的字节数。

后记

这个笔记可能会很潦草和没有规律,主要是为了方便自己使用。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值