网络编程和套接字

网络编程又称为套接字编程,就是编写一段程序,使得两台连网的计算机彼此之间可以交换数据。那么,这两台计算机用什么传输数据呢?首先,需要物理连接,将一台台独立的计算机通过物理线路连接在一起,形成一个网络,使得数据可以通过物理介质进行传输,这一点我们不用过多去关注。我们只需要考虑,如何编写数据传输的程序。编写数据传输的数据,其实说难也难说简单也简单,说它简单,是因为操作系统为我们提供了“套接字”(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. 7天学完Java基础之0/7

    笔记-7天学完Java基础之0/7 1.常用命令提示符(cmd) 启动:Win+R,输入cmd​

  2. cf600E. Lomsat gelral(dsu on tree)

    题意 题目链接 给出一个树,求出每个节点的子树中出现次数最多的颜色的编号和 Sol dsu on tree的裸题. 一会儿好好总结总结qwq #include<bits/stdc++.h> ...

  3. 【干货】Html与CSS入门学习笔记1-3

    从23号开始用了4天时间看完了<Head First Html与CSS>这本书,本书讲解方式深入浅出,便于理解,结合习题,便于记忆,是一本不错的入门书.下面是本书的学习笔记: 一.认识HT ...

  4. JavaScript_HTML DEMO_3_节点

    创建新的HTML元素 删除已有的HTML元素 <body> <div id="div1"> <p id="p1">这是一个段 ...

  5. HDU 1085 Holding Bin-Laden Captive! 活捉本拉登(普通型母函数)

    题意: 有面值分别为1.2.5的硬币,分别有num_1.num_2.num_5个,问不能组成的最小面值是多少?(0<=每种硬币个数<=1000,组成的面值>0) 思路: 母函数解决. ...

  6. IOS UIApplication使用

    - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typica ...

  7. 阿里云服务器下安装LAMP环境(CentOS Linux 6.3) 安装与配置 Apache 服务

    想让我们的阿里云服务器成为一台 Web 服务器,我们需要安装一个 Web 服务器软件,比如 Apache ,或者 Nginx 等等.下面我们就一起来安装一个 Apache 服务. 我们可以使用 yum ...

  8. JT∕T 905 -2014 出租汽车服务管理信息系统的相关协议研究

    出租汽车服务管理信息系统(JT∕T 905 -2014) 国家的相关技术要求2014年7月正式出台,总体有四部分,   第 1 部分:总体技术要求:   第 2 部分:运营专用设备:   第 3 部分 ...

  9. eclipse关闭无用启动项,降低内存占用

    1,我使用的eclipse版本 2.打开windows-->preference 3,勾选掉无用的启动项,我的已经去掉过了, 4,重启eclipse,如果操作后导致一些必须的功能不能用了,可以点 ...

  10. 告诉你今年是哪个生肖年的java程序

    package com.swift; import java.util.Scanner; public class ChineseYear { public static void main(Stri ...