header socket server是什么是什么意思

Android 日记系统logcat内核代码分析_Android中Socket大文件断点下传_C++中单链表的建立跟操作__脚本百事通
稍等,加载中……
^_^请注意,有可能下面的2篇文章才是您想要的内容:
Android 日记系统logcat内核代码分析
Android中Socket大文件断点下传
C++中单链表的建立跟操作
Android 日记系统logcat内核代码分析
Android 日志系统logcat内核代码分析前一篇文章:http://blog.csdn.net/andyhuabing/article/details/8547719 简要介绍了log系统的上层使用方法,本文重点分
析其log内核驱动代码,使得我们对Android日志系统有一个深刻的认识。
内核代码路径:
kernel/drivers/staging/android/logger.h
kernel/drivers/staging/android/logger.c1、Logger驱动程序的相关数据结构
首先来看logger.h头文件的内容:
/* include/linux/logger.h
* Copyright (C)
Google, Inc.
* Author: Robert Love &&
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* GNU General Public License for more details.
#ifndef _LINUX_LOGGER_H
#define _LINUX_LOGGER_H
#include &linux/types.h&
#include &linux/ioctl.h&
struct logger_entry {
/* length of the payload */
__ /* no matter what, we get 2 bytes of padding */
/* generating process's pid */
/* generating process's tid */
/* seconds since Epoch */
/* nanoseconds */
msg[0]; /* the entry's payload */
#define LOGGER_LOG_RADIO "log_radio" /* radio-related messages */
#define LOGGER_LOG_EVENTS "log_events" /* system/hardware events */
#define LOGGER_LOG_SYSTEM "log_system" /* system/framework messages */
#define LOGGER_LOG_MAIN
"log_main" /* everything else */
#define LOGGER_ENTRY_MAX_LEN
#define LOGGER_ENTRY_MAX_PAYLOAD \
(LOGGER_ENTRY_MAX_LEN - sizeof(struct logger_entry))
#define __LOGGERIO 0xAE
#define LOGGER_GET_LOG_BUF_SIZE
_IO(__LOGGERIO, 1) /* size of log */
#define LOGGER_GET_LOG_LEN
_IO(__LOGGERIO, 2) /* used log len */
#define LOGGER_GET_NEXT_ENTRY_LEN _IO(__LOGGERIO, 3) /* next entry len */
#define LOGGER_FLUSH_LOG
_IO(__LOGGERIO, 4) /* flush log */
#endif /* _LINUX_LOGGER_H */
struct logger_entry是一个用于描述一条Log记录的结构体。
其中len成员变量记录了这条记录的有效负载的长度,有效负载指定的日志记录本身的长度,但是不包括用于描述这个记录的struct logger_entry结构体。
从struct logger_entry中也可以看出:优先级别Priority、Tag字符串以及Msg字符串,pid和tid成员变量分别用来记录是哪条进程写入了这条记录。sec和nsec成员变量记录日志写的时间。msg成员变量记录的就有效负载的内容了,它的大小由len成员变量来确定
#define LOGGER_ENTRY_MAX_LEN
#define LOGGER_ENTRY_MAX_PAYLOAD \(LOGGER_ENTRY_MAX_LEN - sizeof(struct logger_entry))
这两个宏定义记录了 最大有效负载长度。
再分析下logger.c实现文件:
* struct logger_log - represents a specific log, such as 'main' or 'radio'
* This structure lives from module insertion until module removal, so it does
* not need additional reference counting. The structure is protected by the
* mutex 'mutex'.
struct logger_log {
unsigned char
*/* the ring buffer itself */
struct miscdevice
/* misc device representing the log */
wait_queue_head_t
/* wait queue for readers */
struct list_head
/* this log's readers */
struct mutex
/* mutex protecting buffer */
w_ /* current write head offset */
/* new readers start here */
/* size of the log */
结构体struct logger_log就是真正用来保存日志的地方了。buffer成员变量变是用保存日志信息的内存缓冲区,它的大小由size成员变量确定。 buffer是一个循环使用的环形缓冲区,缓冲区中保存的内容是以struct
logger_entry为单位的,其组成方式是:
logger_entry | priority | tag | msg
* struct logger_reader - a logging device open for reading
* This object lives from open to release, so we don't need additional
* reference counting. The structure is protected by log-&mutex.
struct logger_reader {
struct logger_log * /* associated log */
struct list_head
/* entry in logger_log's list */
r_ /* current read head offset */
结构体struct logger_reader用来表示一个读取日志的进程,log成员变量指向要读取的日志缓冲区。list成员变量用来连接其它读者进程。r_off成员变量表示当前要读取的日志在缓冲区中的位置。
2、模块初始化过程:
logger是一个misc设备,那么misc设备是个什么东东呢?网上有很多资料,这里简要说明一下:
杂设备——misc
简单的说,杂设备就是内核自动帮你分配设备号并且自动创建设备文件。
1、自动分配设备号,是指所有注册为杂设备的设备的主设备号为10,而次设备号内核自动分配。
2、自动创建设备文件是指,内核会使用udev(前提是你已经移植udev),动态创建设备节点。
int misc_register(struct miscdevice * misc); //注册
int misc_deregister(struct miscdevice *misc); //注销
执行cat /proc/devices命令可以查看此类设备
# cat /proc/devices
Character devices:
4 /dev/vc/0
5 /dev/tty
5 /dev/console
5 /dev/ptmx
10 misc //创建的设备在misc中
在logger这里定义了三个日志设备:
* Defines a log structure with name 'NAME' and a size of 'SIZE' bytes, which
* must be a power of two, greater than LOGGER_ENTRY_MAX_LEN, and less than
* LONG_MAX minus LOGGER_ENTRY_MAX_LEN.
#define DEFINE_LOGGER_DEVICE(VAR, NAME, SIZE) \
static unsigned char _buf_ ## VAR[SIZE]; \
static struct logger_log VAR = { \
.buffer = _buf_ ## VAR, \
.misc = { \
.minor = MISC_DYNAMIC_MINOR, \
.name = NAME, \
.fops = &logger_fops, \
.parent = NULL, \
.wq = __WAIT_QUEUE_HEAD_INITIALIZER(VAR .wq), \
.readers = LIST_HEAD_INIT(VAR .readers), \
.mutex = __MUTEX_INITIALIZER(VAR .mutex), \
.w_off = 0, \
.head = 0, \
.size = SIZE, \
DEFINE_LOGGER_DEVICE(log_main, LOGGER_LOG_MAIN, 64*1024)
DEFINE_LOGGER_DEVICE(log_events, LOGGER_LOG_EVENTS, 256*1024)
DEFINE_LOGGER_DEVICE(log_radio, LOGGER_LOG_RADIO, 64*1024)
DEFINE_LOGGER_DEVICE(log_system, LOGGER_LOG_SYSTEM, 64*1024)
分别是log_main、log_events和log_radio,名称分别LOGGER_LOG_MAIN、LOGGER_LOG_EVENTS和LOGGER_LOG_RADIO这三个不同名称的设备文件操作方法如下:
static const struct file_operations logger_fops = {
.owner = THIS_MODULE,
.read = logger_read,
.aio_write = logger_aio_write,
.poll = logger_poll,
.unlocked_ioctl = logger_ioctl,
.compat_ioctl = logger_ioctl,
.open = logger_open,
.release = logger_release,
日志驱动程序模块的初始化函数为logger_init:
static int __init init_log(struct logger_log *log)
ret = misc_register(&log-&misc);
if (unlikely(ret)) {
printk(KERN_ERR "logger: failed to register misc "
"device for log '%s'!\n", log-&misc.name);
printk(KERN_INFO "logger: created %luK log '%s'\n",
(unsigned long) log-&size && 10, log-&misc.name);
static int __init logger_init(void)
ret = init_log(&log_main);
if (unlikely(ret))
ret = init_log(&log_events);
if (unlikely(ret))
ret = init_log(&log_radio);
if (unlikely(ret))
ret = init_log(&log_system);
if (unlikely(ret))
device_initcall(logger_init);
logger_init函数通过调用init_log函数来初始化了上述提到的三个日志设备,而init_log函数主要调用了misc_register函数来注册misc设备。
3、日志写入重要过程分析:
注册的写入日志设备文件的方法为logger_aio_write
* logger_aio_write - our write method, implementing support for write(),
* writev(), and aio_write(). Writes are our fast path, and we try to optimize
* them above all else.
ssize_t logger_aio_write(struct kiocb *iocb, const struct iovec *iov,
unsigned long nr_segs, loff_t ppos)
struct logger_log *log = file_get_log(iocb-&ki_filp);
size_t orig = log-&w_
struct logger_entry header;
ssize_t ret = 0;
// 下面重点构造 struct logger_entry header 结构体
now = current_kernel_time();
header.pid = current-&
header.tid = current-&
header.sec = now.tv_
header.nsec = now.tv_
header.len = min_t(size_t, iocb-&ki_left, LOGGER_ENTRY_MAX_PAYLOAD);
/* null writes succeed, return zero */
if (unlikely(!header.len))
mutex_lock(&log-&mutex);
* Fix up any readers, pulling them forward to the first readable
* entry after (what will be) the new write offset. We do this now
* because if we partially fail, we can end up with clobbered log
* entries that encroach on readable buffer.
fix_up_readers(log, sizeof(struct logger_entry) + header.len);
do_write_log(log, &header, sizeof(struct logger_entry));
while (nr_segs-- & 0) {
/* figure out how much of this vector we can keep */
len = min_t(size_t, iov-&iov_len, header.len - ret);
/* write out this segment's payload */
nr = do_write_log_from_user(log, iov-&iov_base, len);
if (unlikely(nr & 0)) {
log-&w_off =
mutex_unlock(&log-&mutex);
mutex_unlock(&log-&mutex);
/* wake up any blocked readers */
wake_up_interruptible(&log-&wq);
}首先利用内核信息构造header信息,然后写入:do_write_log(log,
&header, sizeof(struct logger_entry));
然后根据nr_segs数目,通过一个while循环把iov的内容写入到日志缓冲区中,也就是日志的优先级别priority、日志Tag和日志主体Msg。
最后一个重要函数说明:
* Fix up any readers, pulling them forward to the first readable
* entry after (what will be) the new write offset. We do this now
* because if we partially fail, we can end up with clobbered log
* entries that encroach on readable buffer.
fix_up_readers(log, sizeof(struct logger_entry) + header.len);
* fix_up_readers - walk the list of all readers and "fix up" any who were
also do the same for the default "start head".
* We do this by "pulling forward" the readers and start head to the first
* entry after the new write head.
* The caller needs to hold log-&mutex.
static void fix_up_readers(struct logger_log *log, size_t len)
size_t old = log-&w_
size_t new = logger_offset(old + len);
struct logger_reader *
if (clock_interval(old, new, log-&head))
log-&head = get_next_entry(log, log-&head, len);
list_for_each_entry(reader, &log-&readers, list)
if (clock_interval(old, new, reader-&r_off))
reader-&r_off = get_next_entry(log, reader-&r_off, len);
为何需要这么一个函数呢?
由于日志缓冲区是循环使用的,即旧的日志记录如果没有及时读取,而缓冲区的内容又已经用完时,就需要覆盖旧的记录来容纳新的记录。而这部分将要被覆盖的内容,有可能是某些reader的下一次要读取的日志所在的位置,以及为新的reader准备的日志开始读取位置head所在的位置。因此,需要调整这些位置,使它们能够指向一个新的有效的位置。
4、日志读取重要过程分析:
注册的读取日志设备文件的方法为logger_read
* logger_read - our log's read() method
* Behavior:
- O_NONBLOCK works
- If there are no log entries to read, blocks until log is written to
- Atomically reads exactly one log entry
* Optimal read size is LOGGER_ENTRY_MAX_LEN. Will set errno to EINVAL if read
* buffer is insufficient to hold next entry.
static ssize_t logger_read(struct file *file, char __user *buf,
size_t count, loff_t *pos)
struct logger_reader *reader = file-&private_
struct logger_log *log = reader-&
DEFINE_WAIT(wait);
while (1) {
prepare_to_wait(&log-&wq, &wait, TASK_INTERRUPTIBLE);
mutex_lock(&log-&mutex);
ret = (log-&w_off == reader-&r_off);
mutex_unlock(&log-&mutex);
if (file-&f_flags & O_NONBLOCK) {
ret = -EAGAIN;
if (signal_pending(current)) {
ret = -EINTR;
schedule();
finish_wait(&log-&wq, &wait);
mutex_lock(&log-&mutex);
/* is there still something to read or did we race? */
if (unlikely(log-&w_off == reader-&r_off)) {
mutex_unlock(&log-&mutex);
/* get the size of the next entry */
ret = get_entry_len(log, reader-&r_off);
if (count & ret) {
ret = -EINVAL;
/* get exactly one entry from the log */
ret = do_read_log_to_user(log, reader, buf, ret);
mutex_unlock(&log-&mutex);
struct logger_reader *reader = file-&private_ 在这里直接使用 file-&private_data是因为在device open时将private_data赋值为reader,在文件操作方法
logger_open 中:
* logger_open - the log's open() file operation
* Note how near a no-op this is in the write-only case. Keep it that way!
static int logger_open(struct inode *inode, struct file *file)
struct logger_log *
ret = nonseekable_open(inode, file);
log = get_log_from_minor(MINOR(inode-&i_rdev));
return -ENODEV;
if (file-&f_mode & FMODE_READ) {
struct logger_reader *
reader = kmalloc(sizeof(struct logger_reader), GFP_KERNEL);
if (!reader)
return -ENOMEM;
reader-&log =
INIT_LIST_HEAD(&reader-&list);
mutex_lock(&log-&mutex);
reader-&r_off = log-& // 从log-&head位置开始读取日志的,保存在struct logger_reader的成员变量r_off中
list_add_tail(&reader-&list, &log-&readers);
mutex_unlock(&log-&mutex);
file-&private_data =
// 这里对private_data进行了赋值
file-&private_data =
首先在 while (1)循环中判定是否有日志可读,判定语句如下:
= (log-&w_off == reader-&r_off);
即判断当前缓冲区的写入位置和当前读进程的读取位置是否相等,如果不相等,则说明有新的日志可读。
首先通过get_entry_len获取下一条可读的日志记录的长度(日志读取进程是以日志记录为单位进行读取的,一次只读取一条记录):
/* get the size of the next entry */ret = get_entry_len(log, reader-&r_off);
如果其中有数据时则利用do_read_log_to_user执行真正的读取动作:
/* get exactly one entry from the log */ret = do_read_log_to_user(log, reader, buf, ret);
下面我们仔细看下get_entry_len函数:
* get_entry_len - Grabs the length of the payload of the next entry starting
* from 'off'.
* Caller needs to hold log-&mutex.
static __u32 get_entry_len(struct logger_log *log, size_t off)
switch (log-&size - off) {
memcpy(&val, log-&buffer + off, 1);
memcpy(((char *) &val) + 1, log-&buffer, 1);
memcpy(&val, log-&buffer + off, 2);
return sizeof(struct logger_entry) +
}上面这段代码第一次也看不很久,后来想到buffer是一个循环缓冲区终于明白啦!!
我们知道每一条日志记录由两大部分组成,一部分是结构体:struct
logger_entry,另外一部是payload有效负载即打印主体数据。
有效负载长度记录在struct logger_entry中的len字段中,占用两个字节,与结构的struct logger_entry的首地址相同。因此只要读取记录
最前面两个字节就可以了。
1、两个字节连在一起,直接读取即可,所以直接使用 memcpy(&val, log-&buffer + off, 2);
2、两个字节不连在一起,则需要分别读取,这种情况就是读取缓冲区最后一个字节和第一个字节来获取其长度,而此时r_off与size的长度相差1
ok,继续分析真正的数据读取函数:
* do_read_log_to_user - reads exactly 'count' bytes from 'log' into the
* user-space buffer 'buf'. Returns 'count' on success.
* Caller must hold log-&mutex.
static ssize_t do_read_log_to_user(struct logger_log *log,
struct logger_reader *reader,
char __user *buf,
size_t count)
* We read from the log in two disjoint operations. First, we read from
* the current read head offset up to 'count' bytes or to the end of
* the log, whichever comes first.
len = min(count, log-&size - reader-&r_off);
if (copy_to_user(buf, log-&buffer + reader-&r_off, len))
return -EFAULT;
* Second, we read any remaining bytes, starting back at the head of
* the log.
if (count != len)
if (copy_to_user(buf + len, log-&buffer, count - len))
return -EFAULT;
reader-&r_off = logger_offset(reader-&r_off + count);
根据缓冲区中数据分两段的情况,调用copy_to_user函数来把位于内核空间的日志缓冲区指定的内容拷贝到用户空间的内存缓冲区就可以了,同时,把当前读取日志进程的上下文信息中的读偏移r_off前进到下一条日志记录的开始的位置上。
5、其它函数:
logger_poll
用于log用户态调用select函数进行查询,利用
if (log-&w_off != reader-&r_off)ret |= POLLIN | POLLRDNORM;
通知用户是否有有日记需要读取
logger_ioctl 用于一些常用的信息查询,
#define LOGGER_GET_LOG_BUF_SIZE
_IO(__LOGGERIO, 1) /* size of log */
#define LOGGER_GET_LOG_LEN _IO(__LOGGERIO, 2) /* used log len */
#define LOGGER_GET_NEXT_ENTRY_LEN _IO(__LOGGERIO, 3) /* next entry len */
#define LOGGER_FLUSH_LOG _IO(__LOGGERIO, 4) /* flush log */
获取缓冲区中数据长度,下一个日志的记录,比较有意义的是 LOGGER_FLUSH_LOG:
list_for_each_entry(reader, &log-&readers, list)reader-&r_off = log-&w_log-&head = log-&w_
清除缓冲区中的所有数据
Android中Socket大文件断点下传
Android中Socket大文件断点上传什么是Socket?
所谓Socket通常也称作“套接字”,用于描述IP地址和端口,是一个通信连的句柄,应用程序通常通过“套接字”向网络发送请求或者应答网络请求,它就是网络通信过程中端点的抽象表示。它主要包括以下两个协议:
TCP (Transmission Control Protocol 传输控制协议):传输控制协议,提供的是面向连接、可靠的字节流服务。当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。
UDP (User Datagram Protocl 用户数据报协议):用户数据报协议,是一个简单的面向数据报的运输层协议。UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快。
详细解说如下:
TCP传输和UDP不一样,TCP传输是流式的,必须先建立连接,然后数据流沿已连接的线路(虚电路)传输。因此TCP的数据流不会像UDP数据报一样,每个数据报都要包含目标地址和端口,因为每个数据报要单独路由。TCP传输则只需要在建立连接时指定目标地址和端口就可以了。
  形象的讲,TCP就像打电话,UDP就像发电报。宏观上来看UDP是不分客户端和服务端的。通信双方是平等的。微观上来讲只相对一个报文,发送端是客户端,监听端是服务端。发送端把数据报发给路由器就像把电报发给了邮局,后面的事情就是发送者无法控制,也无从知晓的了。所以说是不可靠的,可能会出现报文丢失而无从知晓。就像每张电报都要有收件人一样,每个数据报都要有目的地址和端口。
  而TCP每次连接都是分客户端和服务端的。连接的发起者(相当与拨号打电话的人)是客户端,监听者(相当于在电话边等着接电话的人)是服务端。发起者指定要连接的服务器地址和端口(相当于拨号),监听者通过和发起者三次握手建立连接(相当于听到电话响去接电话)。建立连接后双方可以互相发送和接受数据(打电话)。
Java如何操作Socket?
值得一提的是,Java分别为TCP和UDP提供了相应的类,TCP是java.net中提供了两个类Socket和ServerSocket,分别用来表示双向连接的客户端和服务端。这是两个封装得非常好的类,使用起来很方便!UDP是java.net.DatagramSocket.
127.0.0.1是回路地址,用于测试,相当于localhost本机地址,没有网卡,不设DNS都可以访问,端口地址在0~65535之间,其中0~1023之间的端口是用于一些知名的网络服务和应用,用户的普通网络应用程序应该使用1024以上的端口.
Socket通信模型如下:
如果大家对Java Socket编程还有模糊的地方抓紧温习(http://blog.csdn.net/shimiso/article/details/8529941),本文不在此赘述,下面我们以最常用的TCP协议举例:
服务器,使用ServerSocket监听指定的端口,端口可以随意指定(由于1024以下的端口通常属于保留端口,在一些操作系统中不可以随意使用,所以建议使用大于1024的端口),等待客户连接请求,客户连接后,会话产生;在完成会话后,关闭连接。
客户端,使用Java socket通信对网络上某一个服务器的某一个端口发出连接请求,一旦连接成功,打开会话;会话完成后,关闭Socket。客户端不需要指定打开的端口,通常临时的、动态的分配一个1024以上的端口。
TCP网络连接模型:
Android客户端程序代分析:
UploadActivity.java
package com.android.
import java.io.F
import java.io.OutputS
import java.io.PushbackInputS
import java.io.RandomAccessF
import java.net.S
import android.app.A
import android.os.B
import android.os.E
import android.os.H
import android.os.M
import android.view.V
import android.view.View.OnClickL
import android.widget.B
import android.widget.EditT
import android.widget.ProgressB
import android.widget.TextV
import android.widget.T
import com.android.service.UploadLogS
import com.android.socket.utils.StreamT
public class UploadActivity extends Activity {
private EditText filenameT
private TextView resulV
private ProgressB
private UploadLogService logS
private boolean start=
private Handler handler = new Handler(){
public void handleMessage(Message msg) {
int length = msg.getData().getInt("size");
uploadbar.setProgress(length);
float num = (float)uploadbar.getProgress()/(float)uploadbar.getMax();
int result = (int)(num * 100);
resulView.setText(result+ "%");
if(uploadbar.getProgress()==uploadbar.getMax()){
Toast.makeText(UploadActivity.this, R.string.success, 1).show();
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
logService = new UploadLogService(this);
filenameText = (EditText)this.findViewById(R.id.filename);
uploadbar = (ProgressBar) this.findViewById(R.id.uploadbar);
resulView = (TextView)this.findViewById(R.id.result);
Button button =(Button)this.findViewById(R.id.button);
Button button1 =(Button)this.findViewById(R.id.stop);
button1 .setOnClickListener(new OnClickListener() {
public void onClick(View v) {
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
String filename = filenameText.getText().toString();
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
File uploadFile = new File(Environment.getExternalStorageDirectory(), filename);
if(uploadFile.exists()){
uploadFile(uploadFile);
Toast.makeText(UploadActivity.this, R.string.filenotexsit, 1).show();
Toast.makeText(UploadActivity.this, R.string.sdcarderror, 1).show();
* 上传文件
* @param uploadFile
private void uploadFile(final File uploadFile) {
new Thread(new Runnable() {
public void run() {
uploadbar.setMax((int)uploadFile.length());
String souceid = logService.getBindId(uploadFile);
String head = "Content-Length="+ uploadFile.length() + ";filename="+ uploadFile.getName() + ";sourceid="+
(souceid==null? "" : souceid)+"\r\n";
Socket socket = new Socket("192.168.1.78",7878);
OutputStream outStream = socket.getOutputStream();
outStream.write(head.getBytes());
PushbackInputStream inStream = new PushbackInputStream(socket.getInputStream());
String response = StreamTool.readLine(inStream);
String[] items = response.split(";");
String responseid = items[0].substring(items[0].indexOf("=")+1);
String position = items[1].substring(items[1].indexOf("=")+1);
if(souceid==null){//代表原来没有上传过此文件,往数据库添加一条绑定记录
logService.save(responseid, uploadFile);
RandomAccessFile fileOutStream = new RandomAccessFile(uploadFile, "r");
fileOutStream.seek(Integer.valueOf(position));
byte[] buffer = new byte[1024];
int len = -1;
int length = Integer.valueOf(position);
while(start&&(len = fileOutStream.read(buffer)) != -1){
outStream.write(buffer, 0, len);
Message msg = new Message();
msg.getData().putInt("size", length);
handler.sendMessage(msg);
fileOutStream.close();
outStream.close();
inStream.close();
socket.close();
if(length==uploadFile.length()) logService.delete(uploadFile);
} catch (Exception e) {
e.printStackTrace();
}).start();
StreamTool.java
package com.android.socket.
import java.io.ByteArrayOutputS
import java.io.F
import java.io.FileOutputS
import java.io.IOE
import java.io.InputS
import java.io.PushbackInputS
public class StreamTool {
public static void save(File file, byte[] data) throws Exception {
FileOutputStream outStream = new FileOutputStream(file);
outStream.write(data);
outStream.close();
public static String readLine(PushbackInputStream in) throws IOException {
char buf[] = new char[128];
int room = buf.
int offset = 0;
while (true) {
switch (c = in.read()) {
case '\n':
case '\r':
int c2 = in.read();
if ((c2 != '\n') && (c2 != -1)) in.unread(c2);
if (--room & 0) {
char[] lineBuffer =
buf = new char[offset + 128];
room = buf.length - offset - 1;
System.arraycopy(lineBuffer, 0, buf, 0, offset);
buf[offset++] = (char)
if ((c == -1) && (offset == 0))
return String.copyValueOf(buf, 0, offset);
* @param inStream
* @return 字节数组
* @throws Exception
public static byte[] readStream(InputStream inStream) throws Exception{
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = -1;
while( (len=inStream.read(buffer)) != -1){
outSteam.write(buffer, 0, len);
outSteam.close();
inStream.close();
return outSteam.toByteArray();
UploadLogService.java
package com.android.
import java.io.F
import android.content.C
import android.database.C
import android.database.sqlite.SQLiteD
public class UploadLogService {
private DBOpenHelper dbOpenH
public UploadLogService(Context context){
this.dbOpenHelper = new DBOpenHelper(context);
public void save(String sourceid, File uploadFile){
SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
db.execSQL("insert into uploadlog(uploadfilepath, sourceid) values(?,?)",
new Object[]{uploadFile.getAbsolutePath(),sourceid});
public void delete(File uploadFile){
SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
db.execSQL("delete from uploadlog where uploadfilepath=?", new Object[]{uploadFile.getAbsolutePath()});
public String getBindId(File uploadFile){
SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
Cursor cursor = db.rawQuery("select sourceid from uploadlog where uploadfilepath=?",
new String[]{uploadFile.getAbsolutePath()});
if(cursor.moveToFirst()){
return cursor.getString(0);
DBOpenHelper.java
package com.android.
import android.content.C
import android.database.sqlite.SQLiteD
import android.database.sqlite.SQLiteOpenH
public class DBOpenHelper extends SQLiteOpenHelper {
public DBOpenHelper(Context context) {
super(context, "upload.db", null, 1);
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE uploadlog (_id integer primary key autoincrement, uploadfilepath varchar(100), sourceid varchar(10))");
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS uploadlog");
onCreate(db);
&?xml version="1.0" encoding="utf-8"?&
&LinearLayout xmlns:android="/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/filename"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="022.jpg"
android:id="@+id/filename"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/button"
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="暂停"
android:id="@+id/stop"
&ProgressBar
android:layout_width="fill_parent"
android:layout_height="20px"
style="?android:attr/progressBarStyleHorizontal"
android:id="@+id/uploadbar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:id="@+id/result"
&/LinearLayout&
AndroidManifest.xml
&?xml version="1.0" encoding="utf-8"?&
&manifest xmlns:android="/apk/res/android"
package="com.android.upload"
android:versionCode="1"
android:versionName="1.0" &
&uses-sdk android:minSdkVersion="8" /&
&application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" &
android:name=".UploadActivity"
android:label="@string/app_name" &
&intent-filter&
&action android:name="android.intent.action.MAIN" /&
&category android:name="android.intent.category.LAUNCHER" /&
&/intent-filter&
&/activity&
&/application&
&!-- 访问网络的权限 --&
&uses-permission android:name="android.permission.INTERNET"/&
&!-- 在SDCard中创建与删除文件权限 --&
&uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/&
&!-- 往SDCard写入数据权限 --&
&uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/&
&/manifest&
Java服务端:
SocketServer.javapackage com.android.socket.
import java.io.F
import java.io.FileInputS
import java.io.FileOutputS
import java.io.IOE
import java.io.OutputS
import java.io.PushbackInputS
import java.io.RandomAccessF
import java.net.ServerS
import java.net.S
import java.text.SimpleDateF
import java.util.D
import java.util.HashM
import java.util.M
import java.util.P
import java.util.concurrent.ExecutorS
import java.util.concurrent.E
import com.android.socket.utils.StreamT
public class SocketServer {
private String uploadPath="D:/uploadFile/";
private ExecutorService executorS// 线程池
private ServerSocket ss =
// 监听端口
// 是否退出
private Map&Long, FileLog& datas = new HashMap&Long, FileLog&();// 存放断点数据,最好改为数据库存放
public SocketServer(int port) {
this.port =
// 初始化线程池
executorService = Executors.newFixedThreadPool(Runtime.getRuntime()
.availableProcessors() * 50);
// 启动服务
public void start() throws Exception {
ss = new ServerSocket(port);
while (!quit) {
Socket socket = ss.accept();// 接受客户端的请求
// 为支持多用户并发访问,采用线程池管理每一个用户的连接请求
executorService.execute(new SocketTask(socket));// 启动一个线程来处理请求
public void quit() {
this.quit =
ss.close();
} catch (IOException e) {
e.printStackTrace();
public static void main(String[] args) throws Exception {
SocketServer server = new SocketServer(7878);
server.start();
private class SocketTask implements Runnable {
public SocketTask(Socket socket) {
this.socket =
public void run() {
System.out.println("accepted connenction from "
+ socket.getInetAddress() + " @ " + socket.getPort());
PushbackInputStream inStream = new PushbackInputStream(
socket.getInputStream());
// 得到客户端发来的第一行协议数据:Content-Length=;filename=xxx.3sourceid=
// 如果用户初次上传文件,sourceid的值为空。
String head = StreamTool.readLine(inStream);
System.out.println(head);
if (head != null) {
// 下面从协议数据中读取各种参数值
String[] items = head.split(";");
String filelength = items[0].substring(items[0].indexOf("=") + 1);
String filename = items[1].substring(items[1].indexOf("=") + 1);
String sourceid = items[2].substring(items[2].indexOf("=") + 1);
Long id = System.currentTimeMillis();
FileLog log =
if (null != sourceid && !"".equals(sourceid)) {
id = Long.valueOf(sourceid);
log = find(id);//查找上传的文件是否存在上传记录
File file =
int position = 0;
if(log==null){//如果上传的文件不存在上传记录,为文件添加跟踪记录
String path = new SimpleDateFormat("yyyy/MM/dd/HH/mm").format(new Date());
File dir = new File(uploadPath+ path);
if(!dir.exists()) dir.mkdirs();
file = new File(dir, filename);
if(file.exists()){//如果上传的文件发生重名,然后进行改名
filename = filename.substring(0, filename.indexOf(".")-1)+ dir.listFiles().length+ filename.substring(filename.indexOf("."));
file = new File(dir, filename);
save(id, file);
}else{// 如果上传的文件存在上传记录,读取上次的断点位置
file = new File(log.getPath());//从上传记录中得到文件的路径
if(file.exists()){
File logFile = new File(file.getParentFile(), file.getName()+".log");
if(logFile.exists()){
Properties properties = new Properties();
properties.load(new FileInputStream(logFile));
position = Integer.valueOf(properties.getProperty("length"));//读取断点位置
OutputStream outStream = socket.getOutputStream();
String response = "sourceid="+ id+ ";position="+ position+ "\r\n";
//服务器收到客户端的请求信息后,给客户端返回响应信息:sourceid=4;position=0
//sourceid由服务生成,唯一标识上传的文件,position指示客户端从文件的什么位置开始上传
outStream.write(response.getBytes());
RandomAccessFile fileOutStream = new RandomAccessFile(file, "rwd");
if(position==0) fileOutStream.setLength(Integer.valueOf(filelength));//设置文件长度
fileOutStream.seek(position);//移动文件指定的位置开始写入数据
byte[] buffer = new byte[1024];
int len = -1;
int length =
while( (len=inStream.read(buffer)) != -1){//从输入流中读取数据写入到文件中
fileOutStream.write(buffer, 0, len);
Properties properties = new Properties();
properties.put("length", String.valueOf(length));
FileOutputStream logFile = new FileOutputStream(new File(file.getParentFile(), file.getName()+".log"));
properties.store(logFile, null);//实时记录文件的最后保存位置
logFile.close();
if(length==fileOutStream.length()) delete(id);
fileOutStream.close();
inStream.close();
outStream.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
if(socket != null && !socket.isClosed()) socket.close();
} catch (IOException e) {}
public FileLog find(Long sourceid) {
return datas.get(sourceid);
// 保存上传记录
public void save(Long id, File saveFile) {
// 日后可以改成通过数据库存放
datas.put(id, new FileLog(id, saveFile.getAbsolutePath()));
// 当文件上传完毕,删除记录
public void delete(long sourceid) {
if (datas.containsKey(sourceid))
datas.remove(sourceid);
private class FileLog {
public FileLog(Long id, String path) {
this.path =
public Long getId() {
public void setId(Long id) {
public String getPath() {
public void setPath(String path) {
this.path =
ServerWindow.javapackage com.android.socket.
import java.awt.BorderL
import java.awt.F
import java.awt.L
import java.awt.event.WindowE
import java.awt.event.WindowL
public class ServerWindow extends Frame{
private SocketS
public ServerWindow(String title){
super(title);
server = new SocketServer(7878);
label = new Label();
add(label, BorderLayout.PAGE_START);
label.setText("服务器已经启动");
this.addWindowListener(new WindowListener() {
public void windowOpened(WindowEvent e) {
new Thread(new Runnable() {
public void run() {
server.start();
} catch (Exception e) {
e.printStackTrace();
}).start();
public void windowIconified(WindowEvent e) {
public void windowDeiconified(WindowEvent e) {
public void windowDeactivated(WindowEvent e) {
public void windowClosing(WindowEvent e) {
server.quit();
System.exit(0);
public void windowClosed(WindowEvent e) {
public void windowActivated(WindowEvent e) {
* @param args
public static void main(String[] args) {
ServerWindow window = new ServerWindow("文件上传服务端");
window.setSize(300, 300);
window.setVisible(true);
StreamTool.javapackage com.android.socket.
import java.io.ByteArrayOutputS
import java.io.F
import java.io.FileOutputS
import java.io.IOE
import java.io.InputS
import java.io.PushbackInputS
public class StreamTool {
public static void save(File file, byte[] data) throws Exception {
FileOutputStream outStream = new FileOutputStream(file);
outStream.write(data);
outStream.close();
public static String readLine(PushbackInputStream in) throws IOException {
char buf[] = new char[128];
int room = buf.
int offset = 0;
while (true) {
switch (c = in.read()) {
case '\n':
case '\r':
int c2 = in.read();
if ((c2 != '\n') && (c2 != -1)) in.unread(c2);
if (--room & 0) {
char[] lineBuffer =
buf = new char[offset + 128];
room = buf.length - offset - 1;
System.arraycopy(lineBuffer, 0, buf, 0, offset);
buf[offset++] = (char)
if ((c == -1) && (offset == 0))
return String.copyValueOf(buf, 0, offset);
* @param inStream
* @return 字节数组
* @throws Exception
public static byte[] readStream(InputStream inStream) throws Exception{
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = -1;
while( (len=inStream.read(buffer)) != -1){
outSteam.write(buffer, 0, len);
outSteam.close();
inStream.close();
return outSteam.toByteArray();
运行效果如下:
Android前端控制:
后台监控日志:
下载后的文件路径:
源码下载地址
转载请标明出处 http://blog.csdn.net/shimiso 欢迎有识之士加入我们的技术交流群:
C++中单链表的建立跟操作
C++中单链表的建立和操作准备数据
准备在链表操作中需要用到的变量及数据结构
示例代码如下:
struct Data
//数据结点类型
struct CLType
//定义链表结构
Data nodeD
Data *nextN
定义了链表数据元素的类型Data以及链表的数据结构CLType。结点的具体数据保存在一个结构Data中,而指针nextNode用来指向下一个结点。
我们可以认为,该链表是一个班级学生的记录,其中key表示学号,name为学生的名字,age为年龄。
追加结点就是在链表末尾增加一个结点。表尾结点的地址部分原来保存的是空地址NULL,此时需要将其设置为新增结点的地址(即原表尾结点指向新增结点),然后将新增节点的地址部分设置为空地址NULL,即新增结点为表尾。
由于一般情况下,链表只有一个头指针head,要在末尾添加结点就需要从头指针head开始逐个检查,直到找到最后一个结点(即表尾)。
追加结点的操作步骤如下:
(1)首先分配内存地址,保存新增结点。
(2)从头指针head开始逐个检查,直到找到最后一个结点(即表尾)。
(3)将表尾结点的地址设置为新增结点的地址。
(4)将新增结点的地址部分设置为空地址NULL,即新增结点成为表尾。
示例代码如下:
CLType * CLAddEnd(CLType *head,Data nodeData)
CLType *node,*
if(!(node = new CLType))
cout&&"分配内存失败!"&&
//分配内存失败
return NULL;
node-&nodeData = nodeD
//保存结点数据
node-&nextNode = NULL;
//设置结点指针为空,即作为表尾
if(head == NULL)
//当链表是空表的时候
while(htemp-&nextNode != NULL)
//查找链表的末尾
htemp = htemp-&nextN
htemp-&nextNode =
输入参数head为链表头指针,输入参数nodeData为结点保存的数据。程序中,使用new关键字申请动态空间,如果内分配成功,node中将保存指向该内存区域的指针。
然后,将传入的nodeData保存到申请的内存区域,并设置该结点指向下一结点的指针值为NULL。
插入头结点
插入头结点就是在链表首部添加结点的过程,和在表尾插入结点相反,这个操作是在表头上插入结点,作为头结点。
插入头结点的步骤如下:
(1)首先分配内存,保存新增的结点。
(2)使新增姐弟那指向头指针head所指向的结点
(3)然后使头指针head指向新增结点
示例代码如下:
CLType *CLAddFirst(CLType *head,Data nodeData)
if(!(node = new CLType))
cout&&"分配内存失败"&&
return NULL;
node-&nodeData = nodeD
//保存结点数据
node-&nextNode =
//指向头指针所指向的指针
//头指针指向新增结点
输入参数head为链表头指针,输入参数nodeData为结点中保存的数据。程序中首先使用new关键字申请一个新的保存结点的内存空间,如果申请成功,node中将保存指向该内存区域的指针。
然后,将传入的nodeData保存到申请的内存区域中,并使新增的结点指向头指针head所指向的结点,然后设置头指针head重新指向新增结点。
查找结点就是在链表结构中查找需要的元素。对于链表结构来说,一般可以分为按照结点序号查找和按照关键字查询两类。
按照结点序号查询
即查询链表中的第多少个结点,其示例代码如下:
CLType *CLFindNodeNum(CLType *head,int k)
int i = 1;
//保存链表头指针
for(i = 1;i&k&&i++)
//找到该结点
htemp = htemp-&nextN
//返回指向第k个结点的指针
输入参数head为链表头指针,输入参数k为要查询的结点的序号。通过序号进行多次循环,获得指向该结点的指针,然后返回指针。
按照关键字查询
即根据链表中结点的某一个关键字进行查询,我们以查询学生的姓名(name)为例,其示例代码如下:
CLType *CLFindNodeKey(CLType *head,string name)
//保存链表头指针
while(htemp)
if(htemp-&nodeData.name == name) //当结点关键字和传入关键字相同
//返回该结点指针
htemp = htemp-&nextN
return NULL;
输入参数head为链表头指针,输入参数name为要查询的同学的姓名。遍历查询所有的同学的姓名,当有结点的姓名与所查询的姓名相同的时候,则返回该结点的指针。
插入结点就是在链表中间部分的位置增加一个结点。
插入结点的步骤如下:
(1)分配内存空间,保存新增的结点。
(2)找到要插入的逻辑位置,也就是找到插在那个结点的后面。
(3)修改插入位置结点的指针,使其指向新增结点,而使新增结点指向原插入位置所指向的结点。
示例代码如下:
CLType *CLInsertNode(CLType *head,int k,Data nodeData)
CLType *node,*
if(!(node = new CLType))
//申请结点
cout&&"申请内存失败"&&
return NULL;
node-&nodeData = nodeD
//保存结点中的数据
nodetemp=CLFindNodeNum(head,k-1);//通过按照结点序号查找函数找到插入点前一个结点(关键结点)
if(nodetemp)
node-&nextNode = nodetemp-&nextN//插入的结点指向关键结点的下一个节点
nodetemp-&nextNode =
//关键结点指向插入点
cout&&"没有找到正确的插入位置"&&
//返回头指针
输入参数head为链表头指针,输入参数findkey为链表中进行查找的结点关键字,找到该结点后将在该结点后面添加结点数据,nodeData为新增结点的数据。程序中首先使用new申请结点空间,然后调用CLFindNodeNum函数查找指向结点,然后执行插入操作。
删除结点就是将链表中的某个结点数据删除,并不影响其位置前后的结点。
删除结点操作的步骤如下:
(1)查找需要删除的结点。
(2)使前一结点指向当前节点的下一结点。
(3)删除该结点
删除结点可以通过结点的序号确定要删除的结点,当然也可以通过结点的关键字确定要删除的结点。
我们以通过关键字删除结点为例,示例代码如下:
int CLDeleteNode(CLType *head,string name)
CLType *node,*
//node用于删除结点的前一个结点
while(htemp)
if(htemp-&nodeData.name == name)//找到关键字,执行删除操作
node-&nextNode = htemp-&nextN//使前一结点指向当前节点的下一结点
//释放该结点的空间(即,删除了结点)
//指向当前节点
htemp = htemp-&nextN
//指向下一个结点
//删除失败
head为链表头指针,输入参数name表示要删除的同学的姓名。程序中,通过一个循环,按关键字在整个链表中查找要删除的结点。如果找到被删除的结点,则设置上一结点(node指针所指结点)指向当前结点(h指针所指结点)的下一个结点,即在逻辑上将该结点删除,然后对该结点执行delete操作,释放结点占用的内存空间,即在物理上将其删除。
计算链表长度
计算链表长度也就是统计链表中结点的数量。顺序表中计算链表长度比较方便,但在链表中链表的长度却需要通过遍历链表来获得,因为链表在物理上不是连续存储的。
示例代码如下:
int CLLength(CLType *head)
int Len = 0;
while(htemp)
//遍历整个数组
//累加结点的数量
htemp = htemp-&nextN
//处理下一个结点
参数head是链表的头指针,程序中通过while来遍历指针,Len作为计数器,通过记录循环的次数,来获得链表的长度,当指针为NULL时截止,然后返回计数器的值。
显示所有结点
遍历所有的结点,并输出。
void CLAllNode(CLType *head)
while(htemp)
//遍历整个数组
nodeData = htemp-&nodeD
//获取结点数据
cout&&"key:"&&nodeData.key&&",name:"&&nodeData.name&&",age:"&&nodeData.age&&
htemp = htemp-&nextN
//处理下一个结点
输出结点的函数,没有返回值,所有定义为void。每次都通过CLType类型的结点获得其nodeData的值
链表操作完整示例
完整示例的代码比较长,要耐心看哈……
#include&iostream&
#include&string&
struct Data
//数据结点类型
struct CLType
//定义链表结构
Data nodeD
CLType *nextN
CLType * CLAddEnd(CLType *head,Data nodeData)
CLType *node,*
if(!(node = new CLType))
cout&&"分配内存失败!"&&
//分配内存失败
return NULL;
node-&nodeData = nodeD
//保存结点数据
node-&nextNode = NULL;
//设置结点指针为空,即作为表尾
if(head == NULL)
//当链表是空表的时候
while(htemp-&nextNode != NULL) //查找链表的末尾
htemp = htemp-&nextN
htemp-&nextNode =
CLType *CLAddFirst(CLType *head,Data nodeData)
if(!(node = new CLType))
cout&&"分配内存失败"&&
return NULL;
node-&nodeData = nodeD
//保存结点数据
node-&nextNode =
//指向头指针所指向的指针
//头指针指向新增结点
CLType *CLFindNodeNum(CLType *head,int k)
int i = 1;
//保存链表头指针
for(i = 1;i&k&&i++)
//找到该结点
htemp = htemp-&nextN
//返回指向第k个结点的指针
CLType *CLFindNodeName(CLType *head,string name)
//保存链表头指针
while(htemp)
if(htemp-&nodeData.name == name) //当结点关键字和传入关键字相同
//返回该结点指针
htemp = htemp-&nextN
return NULL;
CLType *CLInsertNode(CLType *head,int k,Data nodeData)
CLType *node,*
if(!(node = new CLType))
//申请结点
cout&&"申请内存失败"&&
return NULL;
node-&nodeData = nodeD
//保存结点中的数据
nodetemp=CLFindNodeNum(head,k-1);
//通过按照结点序号查找函数找到插入点前一个结点(关键结点)
if(nodetemp)
node-&nextNode = nodetemp-&nextN
//插入的结点指向关键结点的下一个节点
nodetemp-&nextNode =
//关键结点指向插入点
cout&&"没有找到正确的插入位置"&&
//返回头指针
int CLDeleteNode(CLType *head,string name)
CLType *node,*
//node用于删除结点的前一个结点
while(htemp)
if(htemp-&nodeData.name == name)
//找到关键字,执行删除操作
node-&nextNode = htemp-&nextN
//使前一结点指向当前节点的下一结点
//释放该结点的空间(即,删除了结点)
//指向当前节点
htemp = htemp-&nextN
//指向下一个结点
//删除失败
int CLLength(CLType *head)
int Len = 0;
while(htemp)
//遍历整个数组
//累加结点的数量
htemp = htemp-&nextN
//处理下一个结点
void CLAllNode(CLType *head)
Data nodeD
cout&&"链表长度为:"&&CLLength(head)&&
while(htemp)
//遍历整个数组
nodeData = htemp-&nodeD
//获取结点数据
cout&&"key:"&&nodeData.key&&",name:"&&nodeData.name&&",age:"&&nodeData.age&&
htemp = htemp-&nextN
//处理下一个结点
int main()
CLType *node,*head = NULL;
Data nodeD
cout&&"请先输入链表中的数据,格式为:学号,姓名,年龄(年龄为0时停止输入)"&&
cin&&nodeData.key&&nodeData.name&&nodeData.
if(nodeData.age==0)
head=CLAddEnd(head,nodeData);
//在链表的尾部添加结点
CLAllNode(head);
//显示所有的结点
//演示在头部插入数据
cout&&"请输入一个结点,并在链表的头部插入"&&
cin&&nodeData.key&&nodeData.name&&nodeData.
head=CLAddFirst(head,nodeData);
CLAllNode(head);
//演示在中间位置插入一个数据
cout&&"请输入一个在链表内部插入的结点:"&&
cin&&nodeData.key&&nodeData.name&&nodeData.
cout&&"请输入插入点的位置:";
head=CLInsertNode(head,k,nodeData);
CLAllNode(head);
//演示按照序号查询数据
cout&&"请输入按照结点查询的一个结点序号:";
node=CLFindNodeNum(head,k);
cout&&"您所查询的结点是:"&&
cout&&"key:"&&node-&nodeData.key&&",name:"&&node-&nodeData.name&&",age:"&&node-&nodeData.age&&
//演示按照姓名查询数据
cout&&"请输入一个按照姓名查询的一个同学的姓名:";
node=CLFindNodeName(head,name);
cout&&"您所查询的结点是:"&&
cout&&"key:"&&node-&nodeData.key&&",name:"&&node-&nodeData.name&&",age:"&&node-&nodeData.age&&
//演示删除数据信息
cout&&"请输入结点中的一个同学中的名字,系统会删除他的信息:";
if(CLDeleteNode(head,name))cout&&"数据删除成功!"&&
CLAllNode(head);
程序运行结果示例:
如果您想提高自己的技术水平,欢迎加入本站官方1号QQ群:&&,&&2号QQ群:,在群里结识技术精英和交流技术^_^
本站联系邮箱:}

我要回帖

更多关于 socket是什么意思 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信