理解网络编程和套接字

Chapter 1 理解网络编程和套接字

1. 网络编程是什么?

编写程序使两台联网的计算机相互交换数据

1.1 构建接电话套接字(服务端程序)

1.1.1调用socket函数(安装电话机)时进行的对话

问:接电话需要准备什么?

答:当然是电话机

#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol);
成功时返回文件描述符,失败时返回-1
1.1.2 调用bind函数(分配电话号码)时进行的对话

问:请问您的电话号码是多少?

答:我的电话号码是123456

#include <sys/socket.h>

int bind(int socket, const struct sockaddr *address,
         socklen_t address_len);
成功时返回0,失败时返回-1

给分配好的socket分配地址信息(IP和端口号)

1.1.3 调用listen函数(连接电话线)时进行的对话

问:已架设完电话机后是否只需要连接电话线?

答:对,只需要连接就可以接听电话

#include <sys/socket.h>

int listen(int sockfd, int backlog);
成功时返回文件描述符,失败时返回-1
1.1.4 调用accept函数(拿起话筒)时进行的对话

问:电话铃响了,我该怎么办?

答:难道您真不知道?接听啊

#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
成功时返回文件描述符,失败时返回-1
1.1.5 总结(服务端程序的流程)
  • 第一步:调用socket函数创建套接字
  • 第二步:调用bind函数绑定IP地址和端口号
  • 第三步:调用listen函数使转化为可接受请求状态
  • 第四步:调用accept函数受理请求连接

1.2 构建打电话套接字(客户端程序)

1.2.1 创建socket
1.2.2 调用connect函数发起连接请求

#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr,
             socklen_t addrlen);
成功时返回0,失败返回-1

1.3 实例

服务端程序:
#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);
    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 <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);
    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);
}

2. 基于Linux的文件操作

​ Linux中一切皆文件,socket自然也是文件。

​ 每当生成文件或socket,操作系统都将返回分配给它们的整数(文件描述符),文件描述符只不过是为了方便的称呼操作系统创建的文件或socket而赋予的数。

​ 分配给标准输入,输出及标准错误的文件描述符

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

​ 其他的文件描述符从3开始的。

2.1 打开文件

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

   int open(const char *path, int oflag, ...);
成功时返回文件描述符,失败时返回-1

path: 文件名的字符串地址

oflag: 文件打开模式信息

打开模式含义
O_CREAT必要时创建文件
O_TRUNC删除全部现有数据
O_APPEND维持现有数据,保存到其后面
O_RDONLY只读打开
O_WRONLY只写打开
O_RDWR读写打开

2.2 关闭文件

#include <unistd.h>

int close(int fildes);

fildes: 需要关闭的文件或socket的文件描述符

2.3 写入文件

#include <unistd.h>

ssize_t write(int fildes, const void *buf, size_t nbyte);

fildes: 显示数据传输对象的文件描述符

buf:保存要传输数据的缓冲地址值

nbyte: 要传输数据的字节数

size_t : unsigned int

ssize_t:signed int

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

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

int main(int argc,char* argv[])
{
    int fd;
    char buf[]="Let's go!\n";

    fd = open("data.txt",O_CREAT | O_WRONLY | O_TRUNC);
    if(fd==-1)
    {
        error_handling("open() error!");
    }

    printf("file descriptor:%d\n",fd);

    if(write(fd,buf,sizeof(buf))==-1)
    {
        error_handling("write() error!");
    }

    close(fd);

    return 0;
}

2.4 读取文件

#include <unistd.h>

ssize_t read(int fildes, void *buf, size_t nbyte);
成功时返回接收的字节数(但遇到文件结尾则返回0),失败时返回-1

fildes:显示数据接收对象的文件描述符

buf:要保存接收数据的缓冲地址值

nbytes:要接收数据的最大字节数

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

#define BUF_SIZE 100

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

int main(int argc,char* argv[])
{
    int fd;
    char buf[BUF_SIZE];

    fd=open("data.txt",O_RDONLY);
    if(fd==-1)
    {
        error_handling("open() error!");
    }

    printf("file descriptor: %d\n",fd);

    if(read(fd,buf,BUF_SIZE)==-1)
    {
        error_handling("read() error!");
    }

    printf("file data:%s",buf);

    close(fd);

    return 0;
}

2.5 文件描述符和socket套接字

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>

int main(void)
{
    int fd1,fd2,fd3;
    fd1 = socket(PF_INET,SOCK_STREAM,0);
    fd2 = open("data.txt",O_CREAT | O_WRONLY|O_TRUNC);
    fd3 = socket(PF_INET,SOCK_STREAM,0);

    printf("file descriptor 1:%d\n",fd1);	// 3
    printf("file descriptor 2:%d\n",fd2);	// 4
    printf("file descriptor 3:%d\n",fd3);	// 5

    close(fd1);
    close(fd2);
    close(fd3);
    
    return 0;
}

文件描述符从3开始由小到大的顺序编号,0,1,2是操作系统分配给了标准I/O的描述符

3. 基于Windows平台实现

3.1 Winsock的初始化

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

#include <winsock2.h>

int WSAStartup(WORD wVersionRequested,LPWSADATA lpWSAData);
成功时返回0,失败时返回非零的错误代码值
  • wVersionRequested Winsock版本信息
  • lpWSAData WSADATA结构体变量的地址值

​ Winsock中存在多个版本,应准备WORD(typedef unsigned int WORD;)类型的套接字版本信息,若版本为1.2,则1是主版本号,2是副版本号,应传递 0x0201

​ 0x0201 高8位为副版本号,低8位为主版本号。这里借助 MAKEWORD宏函数来传递版本信息号。

  • MAKEWORD(1,2); // 主版本号是1,副版本号是2,返回0x0201

Winsock编程的初始化库信息

int main(int argc,char* argv[])
{
	WSADATA wsaData;
	....
	if(WSAStartup(MAKEWORD(1,2),&wsaData)!=0)
	{
		....
	}
	return 0;
}

3.2 注销Winsock相关库

#include <Winsock2.h>

int WSACleanup(void);
成功时返回0,失败时返回 SOCKET_ERROR

3.3 基于windows的套接字(socket)相关函数

3.3.1 创建socket
SOCKET socket(int af,int type,int protocol);
成功时返回套接字句柄(对应linux中的文件描述符),失败返回 INVALID_SOCKET
3.3.2 绑定ip和端口号
int bind(SOCKET s,const struct sockaddr* name,int namelen);
成功返回0,失败返回SOCKET_ERROR
3.3.3 将服务端设置为可接受请求状态
int listen(SOCKET s,int backlog);
成功返回0,失败返回SOCKET_ERROR
3.3.4 受理请求连接(accept)
SOCKET accept(SOCKET s,struct sockaddr* addr,int* addrlen);
成功时返回套接字句柄,失败时返回INVALID_SOCKET
3.3.5 从客户端发起请求连接
int connect(SOCKET s,const struct sockaddr* name,int namelen);
成功时返回0,失败时返回SOCKET_ERROR
3.3.6 关闭套接字
int closesocket(SOCKET s);
成功时返回0,失败时返回SOCKET_ERROR

windows中的句柄相当于linux中的文件描述符,但是windows中的文件句柄和套接字句柄是有区别的。

3.4 基于windows的I/O函数

​ Linux中socket也是文件,因此可以通过文件I/O函数read和write进行数据传输。而Windows严格区分文件I/O函数和套接字I/O函数。

int send(SOCKET s,const char* buf,int len,int flags);
成功时返回传输字节数,失败时返回SOCKET_ERROR
  • s 表示数据传输对象连接的套接字句柄值
  • buf 保存待传输数据的缓冲地址值
  • len 要传输的字节数
  • flags 传输数据时用到的多种选项信息
int recv(SOCKET s, const char* buf, int len, int flags);
成功时返回接收的字节数(收到EOF时为0),失败返回SOCKET_ERROR
  • s 表示数据接收对象连接的套接字句柄值
  • buf 保存待接收数据的缓冲地址值
  • len 要接收的字节数
  • flags 接收数据时用到的多种选项信息

hello_client.c

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

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

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)
	{
		error_handing("WSAStartup() error");
	}

	hSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (hSocket == INVALID_SOCKET)
	{
		error_handing("socket() error");
	}

	memset(&servAddr, 0, sizeof(servAddr));

	servAddr.sin_family = AF_INET;
	servAddr.sin_addr.S_un.S_addr = inet_addr(argv[1]);
	servAddr.sin_port = htons(atoi(argv[2]));

	if (connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
	{
		error_handing("connect() error");
	}

	strlen = recv(hSocket, message, sizeof(message) - 1, 0);
	if (strlen == -1)
	{
		error_handing("recv error");
	}
	printf("Message from server : %s\n", message);

	closesocket(hSocket);

	WSACleanup();

	return 0;

}

hello_server.c

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


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


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 (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
	{
		error_handing("WSAStartup error");
	}

	hServSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (hServSock == INVALID_SOCKET)
	{
		error_handing("socket error!");
	}

	memset(&servAddr, 0, sizeof(servAddr));
	servAddr.sin_family = AF_INET;
	servAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	servAddr.sin_port = htons(atoi(argv[1]));

	if (bind(hServSock, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
	{
		error_handing("bind() error");
	}

	if (listen(hServSock, 5) == SOCKET_ERROR)
	{
		error_handing("listen() error!");
	}

	szClntAddr = sizeof(clntAddr);
	hClntSock = accept(hServSock, (SOCKADDR*)&clntAddr, &szClntAddr);
	if (hClntSock == INVALID_SOCKET)
	{
		error_handing("accept() error");
	}

	send(hClntSock, message, sizeof(message), 0);

	closesocket(hClntSock);
	closesocket(hServSock);
	WSACleanup();

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值