selph
selph
发布于 2022-02-10 / 433 阅读
0
0

TCPIP网络编程ch01--理解网络编程和套接字

本书同时介绍Linux C编程和Windows C编程两种网络编程,这里本人也将按照书上的顺序对这两种网络编程一块进行学习

本章主要是对socket编程有一个主要的认识,然后接下来的章节再进入细节进行学习

初见网络编程和套接字

套接字操作常用函数:

#include <sys/socket.h>
// 套接字由socket函数生成,成功时返回文件描述符,失败返回-1
int socket(int domain, int type, int protocol);

// 给套接字分配地址信息,成功返回0,失败返回-1
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

// 将套接字转化为可接收连接的状态,成功返回0,失败返回-1
int listen(int sockfd, int backlog);

// 接收套接字连接请求,成功返回文件描述符,失败返回-1
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

// 发起套接字连接,成功返回0,失败返回-1
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 

创建接收请求的套接字流程如下:

  1. 调用socket函数创建套接字
  2. 调用bind函数分配端口号和地址
  3. 调用listen函数将套接字转换为可接受连接状态
  4. 调用accept函数接收连接请求

客户端向服务端发起连接流程:

  1. 调用socket函数创建套接字
  2. 调用connect函数向服务器发起请求

服务端运行完成之后无法立即重新运行,如果需要再次运行需要修改端口号,原因后面会说

实验代码

服务端

#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <string.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;

    sockaddr_in serv_addr;
    sockaddr_in clnt_addr;
    socklen_t clnt_addr_size;

    char* message = "hello ,there is selph!";

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

    // 创建服务端socket
    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,(const sockaddr *)&serv_addr,sizeof(serv_addr)) == -1){    
        error_handling("bind() error");
    }

    // 监听地址和端口
    if(listen(serv_sock,5) == -1){
        error_handling("listen() error");
    }

    puts("Server is listening...");

    // 接收客户端请求套接字
    clnt_addr_size = sizeof(clnt_addr);
    clnt_sock = accept(serv_sock,(sockaddr*)&clnt_addr,&clnt_addr_size);
    if(serv_sock == -1)error_handling("accept() error");

    // 向套接字写入内容
    write(clnt_sock, message,strlen(message));

    // 关闭创建的套接字
    close(serv_sock);
    close(clnt_sock);
    
    return 0;
}

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

客户端

#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
void error_handling(char *message);
int main(int argc, char *argv[])
{
    int sock;
    int str_len;
    sockaddr_in serv_addr;

    char message[30] ={0};

    if (argc != 3)
    {
        printf("Usage: %s <IP> <port>\n", argv[0]);
    }

    // 创建socket
    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,(const sockaddr*)&serv_addr,sizeof(serv_addr)) == -1){
        error_handling("connect() error");
    }

    puts("Connecting...");

    // 从套接字读取内容
    str_len = read(sock, message,sizeof(message)-1);
    if(str_len == -1)error_handling("read() error");

    printf("Message From Server(%d byte): %s\n",str_len,message);

    // 关闭套接字
    close(sock);
    return 0;
}

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

运行结果

服务端:

selph@selph:~/NetProgramStudy/ch1$ ./hello_server 8888
Server is listening...

客户端:

selph@selph:~/NetProgramStudy/ch1$ ./hello_client 127.0.0.1 8888
Connecting...
Message From Server(22 byte): hello ,there is selph!

基于Linux的文件操作

在Linux下,一切皆文件,所以套接字也是文件的一种,文件由文件标识符(File Descriptor)来标识,基于Linux系统API的文件操作,使用文件标识符来进行操作;类似在Windows上使用的句柄,Windows上句柄是单独一套操作函数,Linux下则都是使用文件操作函数

实验代码

基本的文件操作的使用:文件打开,写入,修改文件指针,读取

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

int main(int argc,char* argv[]){
    int fd;
    char w_buf[]="Let's it go o o!\n";
    char r_buf[64]={0};

    // 以可读可写,不存在就创建的方式打开文件
    fd = open("data.txt",O_CREAT | O_RDWR | O_TRUNC);
    if(fd == -1)exit(1);

    printf("File Descriptor:%d\n",fd);

    // 写入buf到文件,会修改文件指
    if(write(fd,w_buf,sizeof(w_buf)) == -1)exit(1);

    // 移动文件指针到文件头
    lseek(fd,0,SEEK_SET);

    // 读取文件内容并打印
    if(read(fd,r_buf,sizeof(r_buf)) == -1)exit(1);
    printf("%s\n",r_buf);    

    close(fd);
    return 0;
}

运行结果

selph@selph:~/NetProgramStudy/ch1$ ./"low_file_op" 
File Descriptor:3
Let's it go o o!

基于Windows平台的实现

Windows套接字Winsock,大部分是参考BSD系列UNIX套接字设计的,很多地方跟Linux相似,两大平台的套接字编程很相似,甚至连函数名都很相似

与Linux不同的是,Windows平台套接字编程,需要对winsock进行初始化和注销操作,对socket的IO操作是send和recv,Linux是write和read,其他基本上是一样的就

实验代码

服务端

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#include <WS2tcpip.h>
#pragma comment(lib,"ws2_32.lib")

int main(int argc, char* argv[]) {
	
	SOCKET hSock;
	SOCKADDR_IN servAddr, clntAddr;
	WSADATA wsaData;
	int szClntAddr;
	int str_len;
	char message[64] = { 0 };
	
	if (argc != 3) {
		exit(1);
	}

	// winsock初始化
	if (WSAStartup(MAKEWORD(2, 2), &wsaData))exit(1);

	// 创建socket
	hSock = socket(PF_INET, SOCK_STREAM, 0);
	if (hSock == INVALID_SOCKET)exit(1);

	// 填充服务端地址结构体
	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(hSock, (const sockaddr*)&servAddr, sizeof(sockaddr)) == SOCKET_ERROR)exit(1);

	// 接收信息
	str_len = recv(hSock, message, sizeof(message) - 1, 0);
	if (str_len == -1)exit(1);
	printf("Msg From Server: %s\n",message);
	
	
	// 关闭套接字句柄
	closesocket(hSock);

	// 卸载winsock
	WSACleanup();
	return 0;
}

客户端

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#include <WS2tcpip.h>
#pragma comment(lib,"ws2_32.lib")

int main(int argc, char* argv[]) {
	
	SOCKET hSock;
	SOCKADDR_IN servAddr, clntAddr;
	WSADATA wsaData;
	int szClntAddr;
	int str_len;
	char message[64] = { 0 };
	
	if (argc != 3) {
		exit(1);
	}

	// winsock初始化
	if (WSAStartup(MAKEWORD(2, 2), &wsaData))exit(1);

	// 创建socket
	hSock = socket(PF_INET, SOCK_STREAM, 0);
	if (hSock == INVALID_SOCKET)exit(1);

	// 填充服务端地址结构体
	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(hSock, (const sockaddr*)&servAddr, sizeof(sockaddr)) == SOCKET_ERROR)exit(1);

	// 接收信息
	str_len = recv(hSock, message, sizeof(message) - 1, 0);
	if (str_len == -1)exit(1);
	printf("Msg From Server: %s\n",message);
	
	
	// 关闭套接字句柄
	closesocket(hSock);

	// 卸载winsock
	WSACleanup();
	return 0;
}

运行结果

PS C:\Users\selph\source\Book\TCPIP\ch1\x64\Debug> .\hello_clnt_win.exe 127.0.0.1 9999
Msg From Server: hello selph

参考资料

  • 《TCPIP网络编程》

评论