网络编程和套接字

网络编程又称为套接字编程,就是编写一段程序,使得两台连网的计算机彼此之间可以交换数据。那么,这两台计算机用什么传输数据呢?首先,需要物理连接,将一台台独立的计算机通过物理线路连接在一起,形成一个网络,使得数据可以通过物理介质进行传输,这一点我们不用过多去关注。我们只需要考虑,如何编写数据传输的程序。编写数据传输的数据,其实说难也难说简单也简单,说它简单,是因为操作系统为我们提供了“套接字”(socket)的部件,我们可以通过套接字完成数据在网络中的传输,实现两台计算机互相交换数据,即便我们并不懂socket的原理,这是它的简单之处。但是说它难,是如何使用socket编写高性能的数据传输程序,这是它的难点

那么,为什么要用“套接字”这个词呢?“套接字”包含连接的含义,就像我们把插头插到插座上就能获得电力供给,同样,为了与远程计算机进行数据传输,就需要连接到因特网,而编程中的“套接字”就是用来连接网络的工具  

既然是传输数据,那么就有发送方和接收方,对于套接字来说,发送和接收是有区别的,我们先来讨论用于接收的套接字的创建过程

1.调用socket函数

#include <sys/socket.h>
int socket(int domain, int type, int protocol);

  

创建套接字,成功时返回文件描述符,失败时返回-1

2.调用bind函数

#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);

  

调用bind函数给套接字分配IP地址和端口,成功时返回0,失败时返回-1

3.调用listen函数

#include <sys/socket.h>
int listen(int sockfd, int backlog);

  

将套接字转化为可接收连接的状态,成功时返回0,失败时返回-1

4.调用accept函数

#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

  

等待数据传输,成功时返回文件描述符,失败时返回-1

网络编程中接收连接请求的套接字创建过程如下:

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

编写服务端/客户端套接字

服务器端(server)是能够受理连接请求的程序,下面构建服务端程序以验证之前提到的函数调用过程,该服务端程序收到连接请求后向请求者返回“Hello world!”答复

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(AF_INET, SOCK_STREAM, 0); //<1>
if (serv_sock == -1)
error_handling("sock() 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) //<2>
error_handling("bind() error"); if (listen(serv_sock, 5) == -1) //<3>
error_handling("listen() error"); clnt_addr_size = sizeof(clnt_addr);
clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_addr, &clnt_addr_size); //<4>
if (clnt_sock == -1)
error_handling("accept() error"); write(clnt_sock, message, sizeof(message)); //<5>
close(clnt_sock);
close(serv_sock); return 0;
} void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}

  

  • <1>:调用socket函数创建套接字
  • <2>:调用bind函数分配IP地址和端口号
  • <3>:调用listen函数将套接字转为可接收连接状态
  • <4>:调用accept函数受理连接请求,如果在没有连接请求的情况下调用该函数,则不会返回,直到有连接请求为止
  • <5>:write函数用于传输数据,若程序经过<4>处执行到本行,则说明已有连接请求

编译并运行hello_server.c,创建等待连接请求的服务器端

# gcc hello_server.c -o hello_server
# ls
hello_server hello_server.c
# ./hello_server 8500

  

服务器端创建的套接字又称为服务器端套接字或监听套接字,接下来介绍的套接字是用于请求连接服务器端的客户端套接字

#include <sys/socket.h>
int connect(int sock_fd, struct sockaddr *serv_addr, socklen_t addrlen);

  

connect为客户端用于请求连接的函数,成功时返回0,失败时返回-1

客户端只有“调用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(AF_INET, SOCK_STREAM, 0); //<1>
if (sock == -1)
error_handling("sock() 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) //<2>
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);
}

  

  • <1>:创建套接字,但此时套接字并不马上分为服务器端和客户端,如果下面的代码调用的是bind、listen函数,则是服务器端套接字。如果是connect函数,则是客户端套接字
  • <2>:调用connect函数向服务器端发送连接请求

编译并运行hello_client.c

# gcc hello_client.c -o hello_client
# ls
hello_client hello_client.c hello_server hello_server.c
# ./hello_client 127.0.0.1 8500
Message from server: Hello world!

  

基于Linux的文件操作

对于Linux而言,socket操作与文件操作没有区别。在Linux世界里,socket也被认为是文件的一种,因此在网络数据传输过程中自然可以使用文件I/O的相关函数,如果想使用Linux提供的文件I/O函数,首先应该理解好文件描述符的概念

表1-1   分配给标准输入输出及标准错误的文件描述符
文件描述符 对象
0 标准输入:Standard Input
1 标准输出:Standard Output
2 标准错误:Standard Error

文件和套接字一般经过创建过程才会被分配文件描述符,表1-1中的3种输入输出对象即使未经过特殊的创建过程,程序开始运行后也会被自动分配文件描述符,后面还会讲解其使用方法和含义

打开文件

首先介绍打开文件以读写数据的函数。调用此函数时需传递两个参数:第一个参数是打开的目标文件名及路径信息,第二个参数是文件打开模式(文件特性信息)

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h> int open(const char *path, int flag);//成功时返回文件描述符,失败时返回-1

  

  • path:文件名的字符串地址
  • flag:文件打开模式
表1-2   文件打开模式
打开模式 含义
O_CREAT 必要时创建文件
O_TRUNC 删除全部现有数据
O_APPEND 维持现有数据,并将新数据追加到其后
O_RDONLY 只读打开
O_WRONLY 只写打开
O_RDWR 读写打开

如果flag需要传递多个参数,则通过位或运算(OR)符组合并传递

关闭文件

文件有打开,自然也有关闭。文件是系统中的一种资源,如果我们只打开文件,却不关闭,其他程序就不能使用此资源。且文件打开后,可能有一些操作被缓冲在内存中,若不正常关闭,缓冲在内存中的数据就不能真正写入到文件中,可能造成数据丢失,下面介绍关闭文件时调用的函数

#include <unistd.h>
int close(int fd);//成功时返回文件描述符,失败时返回-1

  

  • fd:需要关闭的文件或套接字的文件描述符

若调用此函数的同时传递文件描述符参数,则关闭相应文件。另外需要注意的是,此函数不仅可以关闭文件,还可以关闭套接字。这再次证明Linux操作系统不区分文件与套接字的特点

将数据写入文件

接下来介绍的write函数用于向文件输出数据。当然,Linux中不区分文件与套接字,因此,通过套接字向其他计算机传输数据时也会调用到该函数

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类型

我们来看下面一段程序, low_open.c将完成创建新文件并保存数据

low_open.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
void error_handling(char *message);
int main(void)
{
int fd;
char buf[] = "Let's go!\n";
fd = open("data.txt", O_CREAT | O_WRONLY | O_TRUNC); //<1>
if (fd == -1)
error_handling("open() error");
printf("file descriptor:%d\n", fd);
if (write(fd, buf, sizeof(buf)) == -1) //<2>
error_handling("write() error!");
close(fd);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
  • <1>:文件打开模式为O_CREAT、O_WRONLY和O_TRUNC的组合,因此将创建空文件,并只能写。若存在data.txt文件,则清空文件的全部数据
  • <2>:向对应于fd中保存的文件描述符的文件传输buf中保存的数据

    

编译并运行low_open.c

# gcc low_open.c -o low_open
# ls
low_open low_open.c
# ./low_open
file descriptor:3
# ls
data.txt low_open low_open.c
# cat data.txt
Let's go!

  

读取文件中的数据

与之前的write函数相对应,read函数是用来接收数据的

include <unistd.h>
ssize_t read(int fd, void *buf, size_t nbytes);//成功时返回接收的字节数(但遇到文件结尾则返回0),失败时返回-1

  

  • fd:显示数据接收对象的文件描述符
  • buf:要保存接收数据的缓冲地址值
  • nbytes:要接收数据的最大字节数

下面,我们通过low_read.c来读取data.txt中保存的数据

low_read.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#define BUF_SIZE 100
void error_handling(char *message);
int main(void)
{
int fd;
char buf[BUF_SIZE];
fd = open("data.txt", O_RDONLY); //<1>
if (fd == -1)
error_handling("open()error");
printf("file descriptor:%d\n", fd);
if (read(fd, buf, sizeof(buf)) == -1) //<2>
error_handling("read()error");
printf("file data:%s", buf);
close(fd);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}

  

  • <1>:打开文件data.txt
  • <2>:调用read函数向第10行中声明的数组buf保存读入的数据

编译并运行low_read.c

# gcc low_read.c -o low_read
[root@bogon tcp_ip]# ls
data.txt low_open low_open.c low_read low_read.c
[root@bogon tcp_ip]# ./low_read
file descriptor:3
file data:Let's go!

  

文件描述符与套接字

下面将同时创建文件和套接字,并用整数形态比较返回的文件描述符值

fd_seri.c

#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("test.dat", O_CREAT | O_WRONLY | O_TRUNC);
fd3 = socket(PF_INET, SOCK_DGRAM, 0);
printf("file descriptor 1:%d\n", fd1);
printf("file descriptor 2:%d\n", fd2);
printf("file descriptor 3:%d\n", fd3);
close(fd1);
close(fd2);
close(fd3);
return 0;
}

  

  • 第8~10行:创建1个文件和2个套接字
  • 第11~13行:输出之前创建的文件描述符的整数值

编译并运行fd_seri.c

# gcc fd_seri.c -o fd_seri
# ./fd_seri
file descriptor 1:3
file descriptor 2:4
file descriptor 3:5

  

从输出的文件描述符整数值可以看出,描述符从3开始由小到大的顺序编号,因为0、1、2是分配给标准I/O的描述符

TCP/IP网络编程之网络编程和套接字的更多相关文章

  1. 【转】PHP实现系统编程(四)--- 本地套接字(Unix Domain Socket)

    原文:http://blog.csdn.net/zhang197093/article/details/78143687?locationNum=6&fps=1 --------------- ...

  2. 基于TCP/IP协议的C++网络编程(API函数版)

    源代码:http://download.csdn.net/detail/nuptboyzhb/4169959 基于TCP/IP协议的网络编程 定义变量——获得WINSOCK版本——加载WINSOCK库 ...

  3. 【网络编程】TCP/IP、UDP、网络概…

    计算机刚刚发明出来的时候,两台计算机之间是无法通信的,为了使计算机之间能够进行数据的交流,制定了OSI(Open SystemInterconnection)开放系统互联模型,而TCP/IP(我们所使 ...

  4. TCP/IP网络编程之优雅地断开套接字

    基于TCP套接字的半关闭 Linux的close函数和Windows的closesocket函数意味着完全断开连接,完全断开连接不仅指无法传输数据,而且也不能接收数据.因此,在某些情况下,通信一方调用 ...

  5. 网络编程(基于udp协议的套接字/socketserver模块/进程简介)

    一.基于UDP协议的套接字 TCP是建立可靠连接,并且通信双方都可以以流的形式发送数据.相对TCP,UDP则是面向无连接的协议. 使用UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就 ...

  6. 【TCP/IP】之Java socket编程API基础

    Socket是Java网络编程的基础,深入学习socket对于了解tcp/ip网络通信协议很有帮助, 此文讲解Socket的基础编程.Socket用法:①.主要用在进程间,网络间通信. 文章目录如下: ...

  7. 【UNIX网络编程(一)】套接字地址结构、网络字节顺序和地址转换功能

    介绍:应该用在网络编程实现每个套接字地址结构.所以主套接字地址结构后前提网络计划编制,地址结构可以在两个方向上发送:从工艺到内核和内核处理.构中的二进制值之间进行转换. 大多数套接字函数都须要一个指向 ...

  8. 设置Windows的TCP/IP属性和内部网络号码

    这里,以Windows XP和Windows 7版本为例. 在安装了IPX/SPX协议或TCP/IP协议的Windows计算机上可以设置计算机的内部网络号码,主要可以防止进行局域网连接时出现冲突现象. ...

  9. 网络知识===《图解TCP/IP》学习笔记——网络的构成要素

    首先引入网络构成要素图 图片来自<图解TCP/IP--P37> 1.通信媒介与数据链路 计算机之间通过电缆相互连接,电缆可以分为多种,包括双绞线电缆,光纤电缆,同轴电缆,串行电缆等. 图片 ...

  10. Java网络编程(一)Socket套接字

    一.基础知识 1.TCP:传输控制协议. 2.UDP:用户数据报协议. 二.IP地址封装 1.InetAddress类的常用方法 getLocalHost() 返回本地主机的InetAddress对象 ...

随机推荐

  1. 在Ubuntu中安装MySQL

    在Ubuntu中安装MySQL Ubuntu实用工具系列文章,将介绍基于Linux ubuntu的各种工具软件的配置和使用.有些工具大家早已耳熟能详,有些工具经常用到但确依然陌生.我将记录我在使用操作 ...

  2. CocoStudio UIButton setPressedActionEnabled(true) 子控件不跟着缩放

    具体情况是这样的:美术给了我 一个按钮的背景图片  一个按钮的文字图片,用背景图片创建一个button,然后把文字图片添加进去(注意关闭文字图片的交互功能) 设置UIButton setPressed ...

  3. Tomcat Stack-8.0.35 (OpenLogic CentOS7.2)

       平台: CentOS 类型: 虚拟机镜像 软件包: apache2.4.20 mysql5.6.30 tomcat8.0.35 apache application server basic s ...

  4. Winform调整DEV控件高度

  5. ffmpeg 命令2

    ffmpeg常用基本命令(转) [FFmpeg]FFmpeg常用基本命令 1.分离视频音频流 ffmpeg -i input_file -vcodec copy -an output_file_vid ...

  6. OpenLayers项目分析——(一)项目介绍

    OpenLayers 是由MetaCarta公司开发的,用于WebGIS客户端的JavaScript包,目前的最高版本是2.7 V,通过BSD License 发行.它实现访问地理空间数据的方法都符合 ...

  7. Android(java)学习笔记79:Android中SimpleAdapter,ArrayAdapter和BaseAdapter常见的适配器

    1. SimpleAdapter(BaseAdapter子类扩展类): simpleAdapter的扩展性最好,可以定义各种各样的布局出来,可以放上ImageView(图片)等.可以显示比较复杂的列表 ...

  8. solr数据分片相关

    solr操作url 使用正常的core,使用命令生成coillection solr create_collection -c students2 -d ../server/solr/my/conf ...

  9. theano 模块 MLP示例

    theano 模块 MLP示例,有需要的朋友可以参考下. theano教程Example: MLP: 约定数组为列向量, 层级:将多层传感器定义为一连串的层级,每个层级定义为一个类.类属性包括:权重. ...

  10. 实用小工具不定期合集(textarea 高度自适应、自动计算Y轴刻度、json转table)

    1.textarea高度自适应 这个非常有用,但是网上的解决方案都不尽人意,话不多说,上代码. function auto (elem) { var minHeight = 30 var change ...