第一章_理解网络编程和套接字
网络编程领域需要一定的操作系统和系统编程知识, 同时还需要理解好TCP/IP 网络数据传输协议. 这么说来, 网络编程的确需要一定的基础知识, 但相对其他领域, 它更有趣, 而且没有想象中这么难. 只要踏踏实实学习, 任何人都可以轻松进入网络编程的世界.
深入细节前, 本章先帮助各位建立对本书的总体认识, 并简要了了解后面的内容. 希望通过本章的学习, 大家能对网络编程有初步了解, 摆脱对它的恐惧.
1.1 理解网络编程和套接字
学习c语言时, 一般会先利用printf函数和scanf函数进行控制台输入输出, 然后学习文件输入输出. 如果各位认真学习过c语言就会发现, 控制台输入和输出非常类似. 实际上, 网络编程也与文件输入输出有很多相似之处, 相信大家也能轻松掌握.
网络编程和套接字概要
网络编程就是编写程序使两台连网的计算机相互交换数据. 这就是全部内容了吗? 是的! 网络编程要比想象中要简单许多. 那么, 这连台计算机之间用什么传输数据呢? 首先需要物理连接. 如今大部分计算机都已连接到庞大的互联网, 因此不用担心这一点. 在此基础上, 只需考虑如何编写数据传输软件. 但实际上这一点也不用愁, 因为操作系统会提供名为"套接字" (socket) 的部件. 套接字是网络数据传输用的软件设备. 即使对网络数据传输原理不太熟悉, 我们也能通过套接字完成数据传输. 因此, 网络编程又称套接字编程. 那为什么要用"套接字"这个词呢?
我们把插头插到插座上就能从电网获得电力供给, 同样, 为了与远程计算机进行数据传输, 需要连接到因特网, 而编程中的"套接字"就是用来连接改网络的工具. 它本身就带有"连接"的含义, 如果将其引申, 则还可以表示两台计算机之间的网络连接.
构建接电话套接字
套接字大致分为两种, 其中, 先要讨论的TCP套接字可以比喻成电话机. 实际上, 电话机也是通过固定电话网(telephone network)完成语音数据交换的. 因此, 我们熟悉的固定电话与套机字并无太大区别. 下面利用电话机讲解套接字的创建及使用方法.
电话机可以同时用来拨打或接听, 但对套机字而言, 拨打和接听是有区别的. 我们先讨论用于接听的套接字创建过程.
调用socket 函数(安装电话机)时进行的对话
问: “接电话需要准备什么?”
答: “当然是电话机”
有了电话机才能安装电话, 接下来, 我们就准备一部漂亮的电话机. 下列函数创建的就是相当于电话机的套机字.
#include <sys/socket.h>
int socket = (int domain, int type, int protocol);
-> 成功时返回文件描述符, 失败时返回-1.
上述函数及本章涉及的其他函数的详解说明将在以后章节中逐一给出, 现在只要掌握"原来是由socket函数生成套接字的" 就足够了. 另外, 我们只需要购买机器, 剩下的安装和分配电话号码等工作都要电信局的工作人员完成. 而套节字需要我们安装, 这也是套接字编程难点所在, 但多安装几次就会发现其实不难. 准备好电话机后要考虑分配电话号码的问题. 这样别人才能联系到自己.
调用bind函数(分配电话号码)时进行的对话
问: “请问您的电话号码是多少?”
答: “我的电话号码是123-1234.”
套接字同样如此. 就像给电话分配电话号码一样(虽然不是真的把电话号码给电话机), 利用以下函数给创建函数好的套接字分配地址信息(IP地址和端口号).
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);
-> 成功时返回 0, 失败时返回 -1.
调用bind函数给套接字分配地址后, 就基本完成了接电话的所有准备工作. 接下来需要连接电话并等待来电.
调用listen函数(连接电话线)时进行的对话
问: "已架设完电话后是否只需连接电话线? "
答: "对, 只需连接就能接听电话. "
一连接电话线, 电话线就转为可接听状态, 这时其他人可以拨打电话请求连接到改机. 同样, 需要把套接字转化成可连接的状态.
#include <sys/socket.h>
int listen(int sockfd, int backlog);
-> 成功时返回0, 失败时返回-1.
连接好电话线后, 如果有人拨打电话就会响铃, 拿起话筒才能接听电话.
调用accept函数(拿起话筒)时进行的对话
问: "电话铃响了, 我该怎么办? "
答: "难道你真的不知道吗 ? 接听啊! "
拿起话筒意味着接收了对方的连接请求. 套接字同样如此, 如果有人为了完成数据传输而请求连接, 就需要调用以下函数进行受理.
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
-> 成功返回文件描述符, 失败时返回-1.
网络编程中接受连接请求的套接字创建过程可整理如下.
- 第一步: 调用socket函数创建套接字.
- 第二步: 调用bind函数分配IP地址和端口号
- 第三步: 调用listen函数转换为可接收请求状态.
- 第四步: 调用accept函数受理连接请求.
记住并掌握这些步骤就相当于为套接字编程勾勒好了轮廓, 后续章节会为此轮廓着色.
编写"hello world" 服务器端
服务器端(server)是能够受理连接请求的程序. 下面构建服务器端以验证之前提到的函数调用过程, 该服务器端收到连接请求后想请求者返回 “hello word” 答复. 除各种函数的调用顺序外, 我们未涉及任何实际编程. 因此, 阅读代码是请重点关注套接字相关函数的调用过程,不必理解全部实例.
#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);
}
编译并运行以上实例, 创建等待连接请求的服务器端. 目前不必详细分析源代码, 只需确认之前4个函数调用过程. 稍后将讲解上述的write函数. 下面讨论如何编写服务器端发送连接请求的客户端.
构造打电话套接字
服务器端创建的套接字又称为服务器套接字或监听(listening)套接字. 接下来介绍的套接字是用请求连接的客户端套接字. 客户端套接字的创建过程比创建服务器端的套接字简单, 因此直接进行讲解.
还未介绍打电话(请求连接)的函数, 因为其调用的是客户端套接字, 如下所示.
#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, socklen_t, addrlen);
-> 成功时返回0, 失败时返回-1;
客户端程序只有"调用socket函数创建套接字"和"调用connect函数向服务器端发送连接请求"这两个步骤, 因此比服务器端简单. 下面给出客户端, 查看以下两项内容: 第一, 调用socket函数和connect函数; 第二, 与服务器端共同运行以发送字符串数据.
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 <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);
}
服务端
客户端
由此查看客户端消息传输的过程. 同时发现, 完成消息传输后, 服务端和客户端都停止运行. 执行过程输入127.0.0.1 是运行实例用的计算机(本地计算机)的IP 地址. 如果在同一台计算机中(本地计算机)的IP 地址. 如果在同一台计算机中同时运行服务器可客户端, 将采用这种连接方式. 但是如果服务端与客户端在不同计算机中运行, 则应采用服务端所在计算机的IP地址.
时间: 2020:05:21